<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>wtrclred</title>
    <link>https://wtrclred.vercel.app/</link>
    <description>Essays, notes, books, and favorite things by ubugeeei.</description>
    <language>en</language>
    <atom:link href="https://wtrclred.vercel.app/rss.xml" rel="self" type="application/rss+xml" />
    <lastBuildDate>Mon, 27 Apr 2026 11:12:35 GMT</lastBuildDate>
    <item>
      <title>Things I Dislike, Things I Like</title>
      <link>https://wtrclred.vercel.app/posts/22</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/22</guid>
      <description>Things ubugeeei dislikes and likes.</description>
      <pubDate>Sun, 19 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-20T05:33:58.000Z</dc:date>
      <category>personal</category>
      <content:encoded><![CDATA[<p>Why write the dislikes first? Because people do not read to the end.</p>
<h2>Things I Dislike</h2>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Human" target="_blank" rel="noopener noreferrer">Humans</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Coffee" target="_blank" rel="noopener noreferrer">Coffee</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Procedure" target="_blank" rel="noopener noreferrer">Procedures</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Document" target="_blank" rel="noopener noreferrer">Documents</a></p>
</li>
<li><p><a href="https://www.milesdavis.com/albums/kind-of-blue/" target="_blank" rel="noopener noreferrer">Kind of Blue</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Interpersonal_relationship" target="_blank" rel="noopener noreferrer">Human relationships</a></p>
</li>
<li><p>Disloyalty</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Monster_Energy" target="_blank" rel="noopener noreferrer">Monster Energy</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Classical_music" target="_blank" rel="noopener noreferrer">Classical music</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Social_norm" target="_blank" rel="noopener noreferrer">Social norms</a></p>
</li>
<li><p>Putting on a false front</p>
</li>
<li><p>Doing things against the <a href="https://en.wikipedia.org/wiki/Law" target="_blank" rel="noopener noreferrer">law</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Private_property" target="_blank" rel="noopener noreferrer">Private ownership</a></p>
</li>
<li><p>Playing the victim</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Parent" target="_blank" rel="noopener noreferrer">Parents</a></p>
</li>
<li><p>Forced chumminess</p>
</li>
<li><p>Inconsistent words and actions</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Violence" target="_blank" rel="noopener noreferrer">Violence</a></p>
</li>
<li><p>Clean <a href="https://en.wikipedia.org/wiki/Internet" target="_blank" rel="noopener noreferrer">internet</a></p>
</li>
<li><p><a href="https://ja.wikipedia.org/wiki/%E3%82%8F%E3%81%8B%E3%81%B0_%28%E3%81%9F%E3%81%B0%E3%81%93%29" target="_blank" rel="noopener noreferrer">Wakaba</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Socialization" target="_blank" rel="noopener noreferrer">Socializing</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Lie" target="_blank" rel="noopener noreferrer">Lies</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Sales" target="_blank" rel="noopener noreferrer">Sales</a></p>
</li>
<li><p><a href="https://wtrclred.vercel.app/posts/19">&quot;It&#39;s all about balance&quot; guys</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Alcoholic_beverage" target="_blank" rel="noopener noreferrer">Alcohol</a> that tastes too much like alcohol</p>
</li>
<li><p>Fatty meat or fish that could give me heartburn</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Beer" target="_blank" rel="noopener noreferrer">Beer</a></p>
</li>
<li><p>Bright people</p>
</li>
<li><p>Noisy eaters</p>
</li>
<li><p>Sticking chopsticks into the same plate (basically impossible for me)</p>
</li>
<li><p>Sharing drinks (basically impossible for me)</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Public_toilet" target="_blank" rel="noopener noreferrer">Public toilets</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Hometown" target="_blank" rel="noopener noreferrer">Hometowns</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Discipline" target="_blank" rel="noopener noreferrer">Discipline</a></p>
</li>
<li><p>Safe choices</p>
</li>
<li><p>Time-based promises</p>
</li>
<li><p>Routine work</p>
</li>
<li><p>Outrage merchants</p>
</li>
<li><p>Getting ready to go out</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Telephone_call" target="_blank" rel="noopener noreferrer">Phone calls</a></p>
</li>
<li><p>Vaguely stylish vibes</p>
</li>
<li><p>Current <a href="https://www.yoshiki.net/" target="_blank" rel="noopener noreferrer">YOSHIKI</a></p>
</li>
<li><p>People who say jazz is stylish when I say I like jazz</p>
</li>
<li><p>Bright places</p>
</li>
<li><p>Joining groups</p>
</li>
<li><p>People who treat jazz as something noble</p>
</li>
<li><p>Shaba-ness</p>
</li>
<li><p>Insincerity</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Absurdity" target="_blank" rel="noopener noreferrer">Unreasonableness</a></p>
</li>
<li><p>Crowded places</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Compromise" target="_blank" rel="noopener noreferrer">Compromise</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Political_correctness" target="_blank" rel="noopener noreferrer">Political correctness</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Liberalism" target="_blank" rel="noopener noreferrer">Liberals</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Privacy" target="_blank" rel="noopener noreferrer">Prying</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Backbiting" target="_blank" rel="noopener noreferrer">Backbiting</a></p>
</li>
<li><p>Trying to look cool</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Authoritarianism" target="_blank" rel="noopener noreferrer">Authoritarianism</a></p>
</li>
<li><p><a href="http://artblakey.com/" target="_blank" rel="noopener noreferrer">Art Blakey</a></p>
</li>
<li><p>Rough gummy candy that gets powder all over my hands</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Lookism" target="_blank" rel="noopener noreferrer">Lookism</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Bullying" target="_blank" rel="noopener noreferrer">Bullying</a></p>
</li>
<li><p>Lack of self-awareness</p>
</li>
</ul>
<h2>Things I Like</h2>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Duck" target="_blank" rel="noopener noreferrer">Ducks</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Compiler" target="_blank" rel="noopener noreferrer">Compilers</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Programming_language_implementation" target="_blank" rel="noopener noreferrer">Language implementations</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Computer" target="_blank" rel="noopener noreferrer">Computers</a></p>
</li>
<li><p>Good taste</p>
</li>
<li><p><a href="https://www.ledzeppelin.com/" target="_blank" rel="noopener noreferrer">Led Zeppelin</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Watercolor_painting" target="_blank" rel="noopener noreferrer">Transparent watercolor</a></p>
</li>
<li><p>Roasty aroma</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Cat" target="_blank" rel="noopener noreferrer">Cats</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Jazz" target="_blank" rel="noopener noreferrer">Jazz</a>, especially <a href="https://en.wikipedia.org/wiki/Contemporary_jazz" target="_blank" rel="noopener noreferrer">Contemporary Jazz</a></p>
</li>
<li><p><a href="https://www.kurtrosenwinkel.com/" target="_blank" rel="noopener noreferrer">Kurt Rosenwinkel</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Consistency" target="_blank" rel="noopener noreferrer">Consistency</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Hip_hop" target="_blank" rel="noopener noreferrer">Hip hop</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Nightclub" target="_blank" rel="noopener noreferrer">Clubs</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Cigarette" target="_blank" rel="noopener noreferrer">Cigarettes</a>, Che is the best</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Sea_urchin" target="_blank" rel="noopener noreferrer">Sea urchin</a></p>
</li>
<li><p>Internet far left</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/List_of_highest_mountains_on_Earth" target="_blank" rel="noopener noreferrer">The highest peak</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Fashion" target="_blank" rel="noopener noreferrer">Fashion</a>, I like <a href="https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%BC%E3%83%89%E7%B3%BB" target="_blank" rel="noopener noreferrer">mode fashion</a></p>
</li>
<li><p>Wearing <a href="https://en.wikipedia.org/wiki/Skirt" target="_blank" rel="noopener noreferrer">skirts</a></p>
</li>
<li><p>Wearing <a href="https://en.wikipedia.org/wiki/Dress" target="_blank" rel="noopener noreferrer">dresses</a></p>
</li>
<li><p>Good stories</p>
</li>
<li><p><a href="https://united-tokyo.com/" target="_blank" rel="noopener noreferrer">UNITED TOKYO</a></p>
</li>
<li><p><a href="https://www.rust-lang.org/" target="_blank" rel="noopener noreferrer">Rust</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Piano_trio" target="_blank" rel="noopener noreferrer">Piano Trio</a></p>
</li>
<li><p><a href="https://www.johncoltrane.com/" target="_blank" rel="noopener noreferrer">John Coltrane</a></p>
</li>
<li><p>Old <a href="https://www.yoshiki.net/" target="_blank" rel="noopener noreferrer">YOSHIKI</a></p>
</li>
<li><p><a href="https://wtrclred.vercel.app/posts/%3Chttps://en.wikipedia.org/wiki/Kunekune_(urban_legend)%3E">Kunekune</a></p>
</li>
<li><p>Researchers with good taste</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Atonality" target="_blank" rel="noopener noreferrer">Atonal</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Music_theory" target="_blank" rel="noopener noreferrer">Music theory</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Satire" target="_blank" rel="noopener noreferrer">Satire</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Concept" target="_blank" rel="noopener noreferrer">Concepts</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Criticism" target="_blank" rel="noopener noreferrer">Criticism</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Sense" target="_blank" rel="noopener noreferrer">Sense</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Thought" target="_blank" rel="noopener noreferrer">Thought</a></p>
</li>
<li><p>Hard Fusion</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Taxonomy" target="_blank" rel="noopener noreferrer">Taxonomy</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Design" target="_blank" rel="noopener noreferrer">Design</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Body_piercing" target="_blank" rel="noopener noreferrer">Piercings</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Interior_design" target="_blank" rel="noopener noreferrer">Interior design</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Nihilism" target="_blank" rel="noopener noreferrer">Nihilism</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Internet" target="_blank" rel="noopener noreferrer">Internet</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Website" target="_blank" rel="noopener noreferrer">Websites</a></p>
</li>
<li><p><a href="https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%91%E3%82%B9" target="_blank" rel="noopener noreferrer">Depas</a></p>
</li>
<li><p><a href="https://www.redbull.com/jp-ja" target="_blank" rel="noopener noreferrer">Red Bull</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Bromazepam" target="_blank" rel="noopener noreferrer">Bromazepam</a></p>
</li>
<li><p><a href="https://www.bradmehldaumusic.com/" target="_blank" rel="noopener noreferrer">Brad Mehldau</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Pancake" target="_blank" rel="noopener noreferrer">Pancakes</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Oil_painting" target="_blank" rel="noopener noreferrer">Oil painting</a></p>
</li>
<li><p>Seven-Eleven renkon nori-shio chips</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Meat" target="_blank" rel="noopener noreferrer">Meat close to raw</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Noodle" target="_blank" rel="noopener noreferrer">The firmer the noodles, the better</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/White_rice" target="_blank" rel="noopener noreferrer">White rice</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Chocolate" target="_blank" rel="noopener noreferrer">Chocolate</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Randomness" target="_blank" rel="noopener noreferrer">Randomness</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Philosophy" target="_blank" rel="noopener noreferrer">Philosophy</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Proposition" target="_blank" rel="noopener noreferrer">Claims</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Neologism" target="_blank" rel="noopener noreferrer">Neologisms</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Determination" target="_blank" rel="noopener noreferrer">Resolve</a></p>
</li>
<li><p>Decisiveness</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Ability" target="_blank" rel="noopener noreferrer">Ability</a></p>
</li>
<li><p>Well-made things</p>
</li>
<li><p><a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">Vue.js</a></p>
</li>
<li><p>Hostile internet</p>
</li>
<li><p>Joke tweets</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Art" target="_blank" rel="noopener noreferrer">Art</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Generative_art" target="_blank" rel="noopener noreferrer">Generative art</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Abstract_art" target="_blank" rel="noopener noreferrer">Abstract painting</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Impressionism" target="_blank" rel="noopener noreferrer">Impressionism</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Tattoo" target="_blank" rel="noopener noreferrer">Tattoos</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Gag" target="_blank" rel="noopener noreferrer">Gags</a></p>
</li>
<li><p>Looking at paintings</p>
</li>
<li><p>Looking at <a href="https://en.wikipedia.org/wiki/Album_cover" target="_blank" rel="noopener noreferrer">album artwork</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Cr%C3%AApe" target="_blank" rel="noopener noreferrer">Crepes</a></p>
</li>
<li><p>Places with dim <a href="https://en.wikipedia.org/wiki/Lighting" target="_blank" rel="noopener noreferrer">lighting</a></p>
</li>
<li><p>Complex things</p>
</li>
<li><p><a href="https://wtrclred.vercel.app/posts/%3Chttps://en.wikipedia.org/wiki/Realism_(arts)%3E">Realism</a></p>
</li>
<li><p>Beautiful things</p>
</li>
<li><p>Towns with good <a href="https://en.wikipedia.org/wiki/Landscape" target="_blank" rel="noopener noreferrer">scenery</a></p>
</li>
<li><p>Alleys with good <a href="https://en.wikipedia.org/wiki/Landscape" target="_blank" rel="noopener noreferrer">scenery</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Walking" target="_blank" rel="noopener noreferrer">Walks</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Drawing" target="_blank" rel="noopener noreferrer">Pencil drawing</a></p>
</li>
<li><p>Systems</p>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>For-Profit Companies Should Pay the People Who Build OSS</title>
      <link>https://wtrclred.vercel.app/posts/21</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/21</guid>
      <description>Companies that make money with OSS should not only sponsor events, but also pay authors, maintainers, and core contributors in a distributed way.</description>
      <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T08:53:41.000Z</dc:date>
      <category>oss</category>
      <category>philosophy</category>
      <category>sponsorship</category>
      <category>web</category>
      <content:encoded><![CDATA[<blockquote>
<p>Note:
This article is my personal opinion and does not represent my employer.
My company&#39;s blog is currently being prepared, and at the moment there was no appropriate place to publish this. Also, because the article is based on my personal concerns, I am publishing it on my personal blog.
This is not meant to blame any specific company or individual.
It is about whether for-profit companies that use OSS should create more financial flow toward its authors, maintainers, and core contributors.</p>
</blockquote>
<h2>Introduction</h2>
<p>The lack of compensation for OSS authors, maintainers, and core contributors becomes a topic again and again. A maintainer of a library is exhausted; an important package is sustained by a very small number of people; companies around the world depend on it, yet the person behind it does not have a stable life because of that work. Whenever such stories appear, the internet becomes uneasy for a moment.</p>
<p>Of course, OSS is not a simple employment contract. Publishing code does not automatically mean that the author has a right to receive payment from every user, and I am not saying that every user should be forced to pay something.</p>
<p>However, for-profit companies are in a slightly different position. We use OSS, build products with it, improve development speed, quality, and operations through it, and create revenue on top of it. Part of that revenue eventually comes back to us as salary or profit.</p>
<p>If that is the case, it seems natural that companies that generate revenue by using OSS should return money to its authors, maintainers, and core contributors.</p>
<h2>This Is Especially True In Web Frontend</h2>
<p>I usually work around web frontend, so I inevitably see this issue through that context.</p>
<p>In modern web frontend development, OSS is not merely a collection of convenient tools. It is part of the premise of daily development and operations. When we prepare a development environment, maintain quality, or deliver a product, we touch OSS in many parts of the process, combine those pieces, adapt them to our own circumstances, and use them as part of our development foundation.</p>
<p>Of course, we also write our own code. There is design, operation, and product understanding, and I do not want to dismiss any of that. Still, much of the foundation is supported by people outside our companies. Many of them create, fix, and release things with their own time, without any direct employment or contractual relationship with us.</p>
<p>OSS can look like an abstract public good, but in reality, behind it there are authors, maintainers, and core contributors who review, decide direction, and fix what breaks. When we talk about who should be paid, I want to bring those people further into the foreground.</p>
<p>In this situation, treating corporate payment to the people who build OSS as a special act of charity feels a little unnatural. It is closer to an ordinary cost. We pay for servers, SaaS, design tools, hiring channels, and event sponsorships.</p>
<p>Then we should also pay the people who build the OSS we depend on.</p>
<h2>Event Support Is More Common Than Support For Builders</h2>
<p>In Japanese tech communities, I often see event sponsorships, and I think this is important. They pay for venues and streaming, improve the experience for speakers and attendees, create contact points between companies and communities, and support the culture in a significant way.</p>
<p>At the same time, compared with the number of companies whose logos appear at events, sponsorships for OSS authors, maintainers, and core contributors still feel relatively rare.</p>
<p>Events are visible. Logos appear, booths appear at venues, they can connect to hiring and branding, and they are easier to explain internally. By contrast, the work of OSS builders is hard to see: reading issues, writing reproduction cases, avoiding breaking changes, updating dependencies, fixing vulnerabilities, writing release notes, and absorbing subtle frustration from users.</p>
<p>This work is not flashy, and it is hard to observe from the outside. But if this steady work stops, our own work may suddenly stop as well.</p>
<p>I am not saying event sponsorships are unnecessary. I want them to continue. What I want to say is that if support tends to concentrate on events, more of it should also go toward the people who create and maintain OSS.</p>
<h2>Maintainers Are Not Only People Inside Large Companies</h2>
<p>Companies such as Meta, Vercel, and Cloudflare have people who work on OSS as part of their jobs. That is a wonderful thing, and it is meaningful for companies to place OSS at the center of their strategy, employ people, and move development forward.</p>
<p>But OSS is not sustained only by that model.</p>
<p>There are many maintainers who do not belong to large companies. Some work as individuals; some work at small companies and maintain projects at night or on weekends; some end up carrying major responsibility for users around the world, even though the project is not their company&#39;s product.</p>
<p>In fact, some OSS that I use in my daily work is created or maintained by developers who do not belong to large companies.</p>
<p>When we think about OSS, we tend to imagine the names of famous companies. But deep inside our dependency graphs, there are individuals whose names and faces we may not know. They are not necessarily employed by large companies, not necessarily fixing things during company time, and not necessarily living with financial room.</p>
<p>Even so, we use what they build to create revenue. We should look more directly at this asymmetry.</p>
<h2>Individual Sponsorship Matters, But It Is Not Enough</h2>
<p>I receive support from around twenty individual sponsors through GitHub Sponsors, and I am grateful for it.</p>
<p>It is a major source of support. It encourages me emotionally, gives me a real sense that people are watching my work, and also gives me a little more financial room.</p>
<p>So I do not think individual sponsorship is meaningless at all. It has a lot of meaning, and the culture of individuals supporting individuals in small amounts should spread further.</p>
<p>However, as a practical matter, it is difficult to live only on small individual sponsorships.</p>
<p>Hundreds or thousands of yen adding up is valuable. But when we consider living costs, taxes, insurance, rent, equipment, and the time needed to keep working, that alone is not enough to sustain ongoing OSS maintenance or core development participation.</p>
<p>This is where companies need to participate.</p>
<p>There are amounts that individuals cannot easily pay, but that are relatively small budgets for companies. An amount of tens of thousands of yen, which is large for an individual, can be close to one business dinner, one SaaS tool, or one hiring initiative for a company. That gap matters, and I think we should use it.</p>
<h2>Large Recurring Sponsorship Has Risks</h2>
<p>This point matters.</p>
<p>Unless a company is truly prepared for it, a single company should not provide large recurring monthly support to one author, maintainer, or core contributor.</p>
<p>At first glance, large recurring support looks desirable. Of course, stable funding itself is good, and it helps the person receiving it.</p>
<p>But if that support becomes too central to their life, the damage is large when it stops.</p>
<p>The company&#39;s policy may change, the person in charge may move to another role, the economy may get worse, the budget may be cut, or the company may stop using that OSS. These things can happen on the company side, but for the person receiving support, they can disrupt life and activity plans.</p>
<p>That is why I think corporate OSS support should be made of many thinner pillars, rather than one concentrated pillar.</p>
<p>One-time sponsorships should happen in a distributed way from many companies. One company supports once, then another company supports next. Once a year is fine, once a quarter is fine, and it can happen when a project releases something or when the company was helped significantly by the project.</p>
<p>Do not turn it into one company&#39;s beautiful story, and do not make one builder depend on one company. Support them broadly and lightly across many companies, but in amounts larger than typical individual sponsorships.</p>
<p>I think this shape is healthier for the economics of OSS.</p>
<h2>Companies Can Support In One-Off Ways</h2>
<p>When people hear that companies should pay the people who build OSS, they may imagine large programs or ongoing contracts.</p>
<p>But it does not have to start as an impressive program. A company can pay a library it often uses this month, a tool that helped with a major migration, a maintainer who responded carefully to an issue, a core contributor who supports important design decisions, an individual deep inside its dependency chain, or a project when a release happens.</p>
<p>That kind of one-time support is enough.</p>
<p>Even if the amount is small for a company, it can be significant for the individual receiving it. For example, support around 50,000, 100,000, or 150,000 JPY may not be excessive as a corporate budget, but for an individual author, maintainer, or core contributor, it can mean a lot.</p>
<p>And if that money does not come continuously from one company, but instead arrives irregularly from multiple companies, the dependency becomes softer.</p>
<p>Of course, accounting may be complicated, and there may be approval processes. There are many questions: whether the recipient uses GitHub Sponsors or Open Collective, whether an invoice is needed, whether it fits internal rules.</p>
<p>Even so, companies should work on it.</p>
<p>We depend on OSS too much to let procedural complexity be the reason we do nothing.</p>
<h2>What I Brought Up At My Company</h2>
<p>Here I want to talk a little about my employer. I usually work as Chief Engineer at Mates, Inc., a for-profit company.</p>
<p>Naturally, we use a lot of OSS at work. In web frontend, backend, and development infrastructure, OSS exists everywhere. Products are built on top of that, revenue is created, and some of it is paid to me as salary.</p>
<p>When I think about that structure, I personally feel strongly that companies should pay the people who build OSS.</p>
<p>However, this article does not represent the company, and it is not the company&#39;s official position. I want to separate that clearly.</p>
<p>With that in mind, I brought this topic up internally, because I thought that the person making the proposal should move first.</p>
<p>&quot;Since we are supported by OSS, could the company also sponsor its authors, maintainers, and core contributors in one-off ways from time to time?&quot;</p>
<p>That is the rough summary of what I asked, and thankfully, people inside the company agreed with it.</p>
<p>I do not want to frame this as an overly beautiful story. The company has not created a huge fund, and a world-changing program is not starting today. The amount will probably be small from a company perspective.</p>
<p>Still, I think there is meaning in starting.</p>
<p>Start from the place where I am, and then, if possible, involve other companies as well. Do not make it one company&#39;s admirable initiative. Make it something many companies naturally do.</p>
<p>That is the direction I hope we move toward.</p>
<h2>This Is Payment More Than Charity</h2>
<p>OSS support easily creates the impression that a company is &quot;doing something good.&quot; It can become a story where the sponsoring company is admirable and the supporter is kind.</p>
<p>Of course, there is some truth to that. Supporting people is good.</p>
<p>But for companies, I think we can understand it more calmly.</p>
<p>Pay the people who build what we use, the people who maintain what contributes to our profit, and the people who support our development speed.</p>
<p>This is less charity than ordinary payment.</p>
<p>We might even say that the unnatural state was the one where we had not been paying enough.</p>
<p>Being able to use something for free does not mean that maintaining it costs nothing. Being able to use something for free under its license does not mean it is economically right to keep free-riding forever.</p>
<p>OSS is often free to use, and that is exactly why those who can pay should voluntarily pay.</p>
<p>Especially companies that generate revenue by using OSS.</p>
<h2>Conclusion</h2>
<p>For-profit companies should pay the people who build OSS.</p>
<p>This is not a phrase meant to attack companies. It is a phrase for looking more accurately at the foundation of our own work.</p>
<p>We use OSS, and OSS gives us development speed, quality, and revenue.</p>
<p>Then part of that should go back to OSS authors, maintainers, and core contributors.</p>
<p>Event sponsorships should continue, individual sponsorships should spread, and the movement of large companies employing OSS developers should continue.</p>
<p>On top of that, I want more for-profit companies to pay the authors, maintainers, and core contributors of the OSS they depend on, in one-off and distributed ways, with amounts a little larger than typical individual sponsorships.</p>
<p>Instead of one company standing too far in front, many companies should naturally pay. Instead of tying one builder to one company, many companies should support them together. Instead of treating it as decoration for hiring or branding, treat it as part of development cost.</p>
<p>I hope the economics of OSS move even a little in this direction.</p>
<p>And I want to help create that flow from the place where I am.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Young People, Make the Internet Messy</title>
      <link>https://wtrclred.vercel.app/posts/20</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/20</guid>
      <description>Do not lie, apologize when wrong, and still have claims, fight, and sharpen them.</description>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-15T11:10:37.000Z</dc:date>
      <category>philosophy</category>
      <content:encoded><![CDATA[<blockquote>
<p>Note: By &quot;make the internet messy,&quot; I do not mean harass people, spread lies, or hurt anyone. I also do not mean treating interests, politics, or people&#39;s survival lightly. I mean the opposite: lying is bad, and if you are wrong, apologize. After that, still have a claim, fight, and sharpen it.</p>
</blockquote>
<h2>Introduction</h2>
<p>I am a software engineer born in 2000. I grew up watching our internet seniors fight on blogs, perform professional wrestling on Twitter, write long posts on forums and comment sections, clash over ideas at meetups, and sometimes become deeply annoying human beings. I also grew up reading 2channel on a Nintendo 3DS.</p>
<p>Of course, I do not want to romanticize the old internet too much. A lot of it was simply awful: there were words that hurt people, lazy assumptions, and attacks that were not funny at all. I am not asking anyone to restore that world as-is. But one thing worries me. <strong>Internet people of my generation may be too quiet.</strong></p>
<h2>Quietness Is A Virtue, But Also A Limit</h2>
<p>Fighting is bad. We should respect each other, we must not harass people, and we must not turn someone&#39;s identity or survival into a game piece. All of that is true, and all of it matters.</p>
<p>But it does not follow that we should have no claims. It does not follow that we should never be controversial, or that we should sit inside safe air and exchange only thin agreement. Respect does not mean avoiding collision; it means treating the other person as human and still bringing your claim to the table.</p>
<p>Saying nothing is not always kindness, either. In a place where everyone avoids claims, wrong assumptions remain, weak designs remain, and boring culture remains. If the internet is always windless, maybe it is not peaceful. Maybe it is just poorly ventilated.</p>
<h2>Do Not Lie, But Be Wrong</h2>
<p>I want to separate two things. Lying is bad. Knowingly spreading misinformation, asserting what you have not checked, or twisting facts to damage someone is simply bad.</p>
<p>But you will be wrong sometimes. If you make strong claims, you increase the chance of being wrong; if you introduce a new concept, it will be rough. If you argue against someone, you may misread them, and in the middle of a discussion your own assumptions may collapse. When that happens, apologize, correct yourself, withdraw the claim, and do a little better next time.</p>
<p>&quot;I might be wrong, so I will say nothing&quot; can look honest, but if you push that attitude too far, you will put nothing into the world. Do not lie, but do not fear being wrong too much. A mistaken claim can be sharpened through apology and correction. A claim that never existed becomes nothing.</p>
<h2>Professional Wrestling Requires Skill</h2>
<p>I want more professional wrestling on the internet, but professional wrestling is not a brawl. It is an advanced form, with breakfall, distance, an audience, and trust in the opponent. Online, too, you need the skill to hit the claim rather than the person, reconstruct the strongest version of the opponent before arguing, leave an exit when you provoke, and avoid being sloppy with facts even when your metaphor is funny.</p>
<p>Apologizing when wrong and stopping when the other person is truly being hurt are part of that skill. If you cannot do these things, it becomes mere attack. That is not professional wrestling; that is an amateur bringing a knife into the ring. Dangerous, and deeply uncool.</p>
<p>That is why we need to develop the skill. If you use strong words, carry strong ethics; if you cause controversy, handle the cleanup; if your words gather people, think about the temperature after they gather. Making the internet messy does not mean destroying the place. It means creating movement in it.</p>
<h2>Have A Claim</h2>
<p>Younger generations probably understand the risk of failure very well. Screenshots remain, posts are searchable, employers can be found, context gets cut away, and someone&#39;s anger can be amplified somewhere you cannot see. So I understand why people become quiet. I feel it too. The internet has become much more like society.</p>
<p>Being loud inside a socialized internet is heavier than making noise behind a handle. Still, you should have claims. This technology should be like this; this culture is boring; this design is weak; this trend is strange; this word is too convenient and dangerous; this community can be better. Claims like that should enter the world more often.</p>
<p>Of course you need reasons, honesty, and readiness to receive pushback. But without claims, discussion does not start; without discussion, nothing deepens; without depth, the world does not change.</p>
<h2>Fight, And Sharpen</h2>
<p>Conflict is not evil by itself. What is evil is conflict that breaks people, attacks identity, destroys someone with lies, or burns irreversible things as a joke.</p>
<p>But there is also conflict between claims, between concepts, and between design philosophies. We need that. A claim that never collides may look round, but a stone that is only round does not cut. It needs to be scraped, chipped, and sharpened before it becomes a tool.</p>
<p>Claims are the same. Pushback reveals weak points, questions reveal assumptions, being laughed at knocks off excessive self-importance, and when your words cut someone, you finally see the direction of the blade. So fight, and sharpen. Do not fight only to win, do not make noise only to burn things, and do not write long essays only to prove yourself right. Fight to change the world a little; make the internet messy to make your claim better.</p>
<h2>Conclusion</h2>
<p>Young people, make the internet messy. But do not lie, do not break people, and do not turn survival or identity into a game. If you are wrong, apologize, correct yourself, withdraw, and still have a claim.</p>
<p>It is fine to be more controversial, to perform more professional wrestling, and to deepen more arguments. Being quiet does not make the world clean; it only makes it quiet.</p>
<p>A quiet internet may look mature at first glance, but what we need is not windless maturity. We need well-managed turbulence. Have a claim, fight, sharpen, and, if possible, change the world a little.</p>
]]></content:encoded>
    </item>
    <item>
      <title>I Hate the Basically It&apos;s Balance Guy</title>
      <link>https://wtrclred.vercel.app/posts/19</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/19</guid>
      <description>On neutrality as a performance, and the honesty of saying you do not yet have a position.</description>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-15T10:32:32.000Z</dc:date>
      <category>philosophy</category>
      <content:encoded><![CDATA[<blockquote>
<p>Note:
The &quot;guy&quot; here is not an age or gender category.
It includes women, young people, and, occasionally, me.
This is not a demographic.
It is the name of an attitude that appears during discussion.</p>
</blockquote>
<h2>Introduction</h2>
<p>I do not like the phrase &quot;basically, it&#39;s balance.&quot;</p>
<p>Of course, I do not hate balance itself.
Quality and speed, freedom and discipline, safety and mobility, ideals and reality.
These debates usually do end up as questions of balance.</p>
<p>That is exactly why saying &quot;basically, it&#39;s balance&quot; in the middle of a discussion often means almost nothing.</p>
<p>It is like telling someone who is cooking, &quot;Basically, it&#39;s heat control.&quot;
Correct.
Too correct.
And it helps nobody&#39;s hands move even one millimeter.</p>
<p>High heat or low heat?
Now or later?
Are we searing the surface, or cooking it through?
When people are discussing exactly that, &quot;basically, it&#39;s heat control&quot; only lets everyone answer &quot;yes.&quot;
And the moment everyone says &quot;yes,&quot; the discussion dies.</p>
<h2>If You Have No Claim, Say So</h2>
<p>Having no claim is not bad.</p>
<p>If you lack information, say you lack information.
If the criteria are unclear, say the criteria are unclear.
If you have not thought enough yet, say, &quot;I do not have a position yet.&quot;</p>
<p>That is honest.</p>
<p>But once &quot;I have no claim&quot; turns into &quot;I am observing this from a higher level,&quot; the situation changes.</p>
<p>You mock both sides.
You say, &quot;Now, now, let&#39;s not get heated.&quot;
You behave as if you alone are the adult in the room.</p>
<p>That is not neutrality.
That is ego in a very polished bottle.</p>
<p>Polite contempt is still contempt.
Poison dusted with sugar becomes more dangerous when someone puts it on the candy shelf.</p>
<h2>Being Unable To State An Opinion Is Not Neutrality</h2>
<p>I want to emphasize this.</p>
<p><strong>Being unable to state an opinion</strong> and <strong>being neutral</strong> are not the same thing.</p>
<p>You may lack knowledge.
You may not be able to take responsibility.
The social dynamics in the room may be scary.
You may not have found the words yet.
There are moments when you simply cannot state an opinion.</p>
<p>That is a state.
It is a limit.
It is normal, because humans are humans.</p>
<p>But neutrality is not a state.
It is a position.
It means seeing both claims and choosing to withhold judgment for now.
It means organizing the issues.
It means separating the information needed for judgment.
It means taking on that role.</p>
<p>Do not treat being unable to state an opinion as if it were neutrality.
That swaps &quot;I cannot place a position yet&quot; for &quot;I have transcended both sides.&quot;</p>
<p>When that swap happens, having no claim somehow becomes moral superiority.
The people participating in the discussion look immature, and you alone look mature.</p>
<p>That is not neutrality.
It is wrapping paper for not being able to participate.</p>
<h2>Discussion Is Not A Fight</h2>
<p>The &quot;basically, it&#39;s balance&quot; guy often sees discussion as fighting.</p>
<p>Person A argues strongly.
Person B pushes back strongly.
Counterexamples appear.
Assumptions get excavated.
The number of issues grows.</p>
<p>Then the situation is read as &quot;getting messy,&quot; and they step in to stop it.</p>
<p>Of course, if there is personal attack or harassment, it should be stopped.
That is no longer discussion.
That is just people moving toward damage.</p>
<p>But disagreement itself is not evil.
In fact, when both sides expose their claims, a landscape appears that could not be seen before.</p>
<p>Someone values speed.
Someone values safety.
Someone brings up operational cost.
Someone brings up user impact.</p>
<p>This is not merely conflict.
The map of issues is growing.
The axes of value are becoming visible.
This is probably what it means for a discussion to deepen.</p>
<p>If someone stops that process with &quot;basically, it&#39;s balance,&quot; the axes that were just starting to grow get snapped.
What remains is a vaguely adult atmosphere.</p>
<p>You cannot put that atmosphere in meeting notes.
You cannot use it for implementation, design, or decision making.
But the speaker&#39;s self-image gets a little warmer.</p>
<p>As a low-energy heater, this may be excellent.
As a contribution to the world, it is suspicious.</p>
<h2>Both Matter Is The Entrance, Not The Exit</h2>
<p>&quot;Both matter&quot; has the same problem.</p>
<p>Of course both matter.
That is why we are discussing them.
If one side obviously did not matter, there would be no discussion in the first place.</p>
<p>The important part comes after that.</p>
<ul>
<li><p>Which side do we prioritize this time?</p>
</li>
<li><p>Under what condition does the priority reverse?</p>
</li>
<li><p>Who pays the cost?</p>
</li>
<li><p>If we fail, which failure is easier to recover from?</p>
</li>
<li><p>Is this a principle, or an exception for this case?</p>
</li>
</ul>
<p>Only at this level does &quot;balance&quot; become work.
Before that, &quot;both matter&quot; is just an entrance ticket.
Please do not stand in the middle of the stage holding only the ticket.
The show has not started.</p>
<p>To choose something is also to not choose something else.
To weigh one side more heavily is to accept the dissatisfaction of the other side.
To balance is also to decide where the debt goes.</p>
<p>Balance without that is a very luxurious form of irresponsibility.</p>
<h2>If You Stop A Discussion, Have A Reason</h2>
<p>Stopping a discussion is not itself bad.</p>
<p>Sometimes there is no time.
Sometimes participants are tired.
Sometimes context is missing.
Sometimes the same issue has looped around three times.</p>
<p>But if you stop a discussion, you should have a reason.</p>
<blockquote>
<p>I want to stop here because we do not have enough information yet.</p>
</blockquote>
<blockquote>
<p>The issue has split into three, so I want to separate them first.</p>
</blockquote>
<blockquote>
<p>This is drifting toward evaluating people, so I want to bring the target back to the proposal.</p>
</blockquote>
<p>These are contributions.
They do not kill the discussion.
They reshape it so it can continue.</p>
<p>By contrast:</p>
<blockquote>
<p>Basically, it&#39;s balance.</p>
</blockquote>
<p>is a stop command disguised as a conclusion.</p>
<p>And &quot;balance&quot; looks virtuous.
So the person resisting it can be made to look like the extreme one.</p>
<p>That is unfair.
Very unfair.
And often the person doing it does not even realize they are doing it.
That lack of self-awareness is part of the problem.</p>
<h2>Conclusion</h2>
<p>What I hate is not balance.</p>
<p>I hate that &quot;basically, it&#39;s balance&quot; is used as an indulgence for not making a claim.</p>
<p>People who make claims risk being wrong.
People who push back risk being disliked.
People who offer concrete proposals risk being criticized concretely.</p>
<p>Stepping in from the side with nothing but &quot;basically, it&#39;s balance&quot; is like showing up to a hot pot party empty-handed and saying, &quot;Flavor matters.&quot;</p>
<p>Yes.
It matters.
That is why everyone is cooking.
At least cut the scallions.</p>
<p>If you have a claim, make it.
If you are organizing, organize.
If you are stopping the discussion, stop it with a reason.
If you do not have a claim yet, say you do not have one.</p>
<p>Discussions do not mature just because they are cooled down.
Sometimes heat is what lets the shape appear.</p>
<p>I do want to avoid burns.
But if we run the world only with people who grab a fire extinguisher the moment they see flame, dinner will probably stay raw forever.</p>
]]></content:encoded>
    </item>
    <item>
      <title>corsa-bind: The Idea of Language Processor Orchestration</title>
      <link>https://wtrclred.vercel.app/posts/17</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/17</guid>
      <description>Treat tsgo&apos;s stdio API not as a plain function-call boundary, but as a set of long-lived language processor servers. This is the idea of language processor orchestration that I am exploring while building corsa-bind.</description>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T08:53:41.000Z</dc:date>
      <category>typescript</category>
      <category>language</category>
      <category>compiler</category>
      <category>architecture</category>
      <category>language-tools</category>
      <category>rust</category>
      <category>oss</category>
      <category>orchestration</category>
      <content:encoded><![CDATA[<h2>Introduction</h2>
<p>When looking at <code>typescript-go</code>, the native implementation of TypeScript, or <code>tsgo</code>, there is a moment where the way you think about it changes quite naturally.</p>
<p>At first, it looks like a story about making <code>tsc</code> faster.
The <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/" target="_blank" rel="noopener noreferrer">TypeScript 6.0 announcement</a> describes TypeScript 6.0 as a bridge release from the current JavaScript implementation toward the Go implementation used by TypeScript 7.0 and beyond, and describes that Go codebase as a new implementation that uses native code and shared-memory multi-threading.</p>
<p>But when I write code that integrates with <code>tsgo</code> from the outside, my interest slowly moves somewhere else.</p>
<p>What I want is not only a faster single run of <code>tsgo --noEmit</code>.
I want a shape where multiple consumers, such as editors, linters, code generators, refactoring tools, AI agents, and build servers, gather around the same TypeScript program graph and checker, then repeatedly ask only the questions they need.</p>
<p>At that point, <code>tsgo</code> starts looking to me less like a CLI and more like a <strong>language processor server</strong>.</p>
<p>And <a href="https://github.com/ubugeeei/corsa-bind" target="_blank" rel="noopener noreferrer">corsa-bind</a> is where I am pushing that view into implementation.
I do not fork <code>tsgo</code> or rewrite its internals.
Instead, I keep <code>corsa-bind</code> on the stdio API and LSP boundaries that upstream provides, then orchestrate workers, sessions, snapshots, transports, and caches around them.</p>
<p>A small note on the name: <code>corsa</code> is not the name of my project by itself.
It comes from the <code>tsgo</code> side&#39;s code name.
My project is <code>corsa-bind</code>, and its <a href="https://github.com/ubugeeei/corsa-bind#readme" target="_blank" rel="noopener noreferrer">README</a> describes the repository as bindings and orchestration layers for using <code>typescript-go</code> from Rust and Node.js.
In that sense, <code>bind</code> does not mean exposing a single FFI function.
It means building the connection and operations layer around <code>tsgo</code> for Rust, Node, and other native-language surfaces.</p>
<p>I want to call this idea <strong>language processor orchestration</strong>.</p>
<h2>stdio API Is Not a Function Call</h2>
<p>When you first see the <code>tsgo</code> stdio API, you might think it is just &quot;sending requests and responses over standard input and standard output.&quot;
But that view drops something important.</p>
<p>On the other side of stdio, there is a language processor running as another process.
We send requests.
It builds programs, reads source files, resolves types, holds symbols, manages snapshots, and returns responses.</p>
<p>In other words, the boundary looks like this.</p>
<pre><code class="language-text">caller process
  -&gt; transport
  -&gt; tsgo worker process
  -&gt; program / checker / snapshot state
</code></pre>
<p>The important part is that the <code>tsgo</code> worker has state.
If you start a process, read the config, open the project, type-check, and throw everything away each time, that is the CLI way of using it.
But if you keep the worker alive, initialize once, reuse snapshots, and keep sending small queries into the same session, that is the server way of using it.</p>
<p>The <a href="https://docs.rs/corsa_bind_client/latest/corsa_bind_client/" target="_blank" rel="noopener noreferrer"><code>corsa_bind_client</code> docs on docs.rs</a> describe this layer as high-level client bindings for the <code>typescript-go</code> stdio API.
Concretely, it can spawn a <code>tsgo</code> worker process, initialize a session once and reuse it, create and reuse snapshots, and ask type, symbol, and syntax questions through typed helpers.</p>
<p>At that point, what I am doing in <code>corsa-bind</code> is no longer just binding.
It is not simply exposing one function through FFI.
The layer has to deal with process lifetime, transport choice, request types, snapshot handle lifetime, cleanup, timeouts, and observability together.</p>
<p>I am not building only a thin binding for calling a language processor.
I am writing a layer for operating one.</p>
<h2>Change the Shape of Work, Not the Compiler</h2>
<p>There is one misunderstanding to avoid here.</p>
<p>I am not building <code>corsa-bind</code> to become a smarter compiler engine than <code>tsgo</code>.
If you open the same project, resolve the same types, and produce the same diagnostics, the fundamental work is still done by <code>tsgo</code>.
A wrapper does not magically make the engine itself faster.</p>
<p>What I want to change with <code>corsa-bind</code> is the shape of the work.</p>
<p>For example, these two workflows are very different.</p>
<pre><code class="language-text">CLI-shaped workflow:
  run tsgo
  load config
  open project
  build program
  answer one big question
  exit

orchestrated workflow:
  spawn worker
  initialize once
  open project once
  create snapshot
  answer many small questions
  reuse worker and snapshot
  close explicitly
</code></pre>
<p>The first shape is natural for batch processing.
If you are checking the whole project in CI, this is still a very reasonable shape.</p>
<p>The second shape is closer to editors, lint rules, and agents.
You want to know the type of one node.
Then you want to resolve a symbol.
Then you want to slightly change a virtual document and ask again.
Reopening the entire project as a CLI operation every time is like starting a database server for every request in a distributed system.</p>
<p>So the performance model I use for <code>corsa-bind</code> is not &quot;do the same work and beat <code>tsgo</code>.&quot;
It is closer to &quot;reuse the same engine state and avoid unnecessary work.&quot;
The <a href="https://github.com/ubugeeei/corsa-bind/blob/main/docs/benchmarking_guide.md" target="_blank" rel="noopener noreferrer">benchmarking guide</a> follows that line of thought: separate engine speed from wrapper speed, separate cold runs from warm runs, and look at session reuse and transport choices as different questions.</p>
<p>A good claim is not &quot;<code>corsa-bind</code> is faster than <code>tsgo</code>.&quot;
A good claim is &quot;in a warm editor workflow, reusing a live session can be cheaper than rerunning <code>tsgo --noEmit</code> every time.&quot;</p>
<p>That difference may look small, but it is large.
The topic is no longer &quot;how to build a faster compiler.&quot;
It becomes a question of where to place the processing system around the compiler, which state to reuse, and which queries to send to which worker.</p>
<p>In other words, this is not a move away from compilers.
It is a move toward treating a compiler not as a single executable or one-shot function call, but as a long-lived service.
The parser, binder, checker, and program graph remain the core of the compiler, but when to start them, where to keep them, which consumers can share them, and what query granularity to expose become design problems outside the compiler itself.</p>
<p>That outside layer is what I want to work on in <code>corsa-bind</code>.
I leave the compiler engine semantics to upstream <code>tsgo</code>.
Then I design the transport, lifetime, and cache layers around that compiler.</p>
<h2>A Snapshot Is a Handle to Remote State</h2>
<p>One concept I care about in <code>corsa-bind</code> is the snapshot.</p>
<p>A snapshot is not just a JSON value.
At least from my point of view while writing the orchestrator, it is a handle to language processor state living inside the worker.</p>
<p>Very roughly, the relationship looks like this.</p>
<pre><code class="language-text">ApiClient
  owns worker process
  owns transport
  initializes tsgo session

ManagedSnapshot
  points to snapshot state inside that session
  can be reused for type / symbol / syntax queries
  releases remote state when dropped or closed
</code></pre>
<p>The fact that it is a handle matters.</p>
<p>In a normal library call, you pass a value into a function and receive a return value.
But with <code>tsgo</code> over stdio, the heavy state remains on the worker side.
The caller holds a handle that identifies that state, then refers to that handle in the next request.</p>
<p>This is quite close to a database connection or an actor system.
You ask, &quot;for this snapshot, tell me the type at this file position.&quot;
You ask, &quot;create a snapshot that reflects this virtual document.&quot;
You say, &quot;release this, because I do not need it anymore.&quot;</p>
<p>In other words, you are managing the lifetime of language processor internal state from outside the process boundary.
If you handle this casually, correctness breaks before performance even matters.
If you forget to release snapshots, resources remain on the worker side.
If you treat process cleanup as unimportant, you can leave zombie processes behind or distort later benchmarks.</p>
<p>That is why I bring operational concerns like timeouts, graceful shutdown, observer events, and queue capacity into the same design surface as the typed client in <code>corsa-bind</code>.
It is less a binding and more a small operations layer.</p>
<h2>Think Like Distributed Servers</h2>
<p>If I push this idea one step further, <code>corsa-bind</code> starts to resemble a distributed server system to me.</p>
<p>Of course, if you are only spawning several <code>tsgo</code> workers on one machine, it is not literally a distributed system over the network.
But the design problems are very similar.</p>
<ul>
<li><p>Which worker should receive this request?</p>
</li>
<li><p>When should a worker start, and when should it stop?</p>
</li>
<li><p>How should cold workers and warm workers be treated?</p>
</li>
<li><p>Which snapshots can be reused?</p>
</li>
<li><p>If a worker crashes, how much can be recovered?</p>
</li>
<li><p>What should happen to a timed-out request?</p>
</li>
<li><p>Should the transport be JSON-RPC or msgpack?</p>
</li>
<li><p>At what granularity should observable events be emitted?</p>
</li>
</ul>
<p>This is not just &quot;calling a compiler API.&quot;
It is closer to operating a small cluster.</p>
<p>For example, a concept like <code>ApiProfile</code> is not merely a name for a spawn config.
It is a stable way to describe the character of a worker: which <code>tsgo</code> executable to use, which cwd to start it in, which transport to use, and which timeout and observer to attach.</p>
<p>Conceptually, it looks like this.</p>
<pre><code class="language-text">profile: &quot;default-msgpack&quot;
  -&gt; worker pool
    -&gt; worker 1
      -&gt; session
      -&gt; snapshots
    -&gt; worker 2
      -&gt; session
      -&gt; snapshots

profile: &quot;lsp-jsonrpc&quot;
  -&gt; worker pool
    -&gt; worker 1
      -&gt; lsp session
      -&gt; virtual documents
</code></pre>
<p>It might be an overstatement to say that workers behave like database shards.
But in the sense that an orchestrator on the client side manages remote, stateful processing entities, much of the same design vocabulary becomes useful.</p>
<p>A process is a node.
A snapshot is a handle to remote state.
A request is a message.
A transport is a wire format.
A profile is a deployment unit.
An observer is telemetry.
Cleanup is correctness.</p>
<p>When you look at it through this metaphor, language processor API design suddenly becomes much more interesting.</p>
<h2>Why Not Fork Upstream?</h2>
<p>I made the <code>corsa-bind</code> README very explicit about its <code>no forks, no patches</code> policy.
It treats <code>ref/typescript-go</code> as an exact upstream checkout, uses upstream-supported entry points, and does not carry local patches.</p>
<p>This is not just my purism.
If you are building an orchestration layer, it is important not to silently change the semantics of the foundation.</p>
<p>If I patched <code>tsgo</code> itself to make something faster, it would become unclear whether the speed came from <code>corsa-bind</code>&#39;s orchestration or from compiler engine changes.
Benchmarks would become harder to read.
Tracking upstream would become harder.
Compatibility as seen from other language bindings would become more suspicious.</p>
<p>So I split the boundary.</p>
<p><code>tsgo</code> is treated as the upstream engine for language processing.
In <code>corsa-bind</code>, I handle the outside: transport, typed requests and responses, session reuse, snapshot lifetime, Node bindings, C ABI, and other language bindings.</p>
<p>I think this separation is healthy.
Instead of entering the compiler and taking hold of everything, this approach respects the compiler as a specialized server and designs the way it is used around that server.</p>
<p>This is Unix-like, and it is also microservice-like.
The only difference is that the server here is not an HTTP service.
It is a type checker.</p>
<h2>Node Bindings Are an Entry Point for Author Experience</h2>
<p>I put not only the Rust-side client and orchestrator into <code>corsa-bind</code>, but also Node bindings built with <code>napi-rs</code>.
This is not only about making Rust callable from JavaScript.</p>
<p>The authors of tools that use TypeScript type information often live on the JS / TS side.
People writing lint rules, code mods, editor extensions, framework plugins, and AI agent tool adapters do not necessarily want to touch a Rust crate directly.</p>
<p>At the same time, writing heavy protocol handling, process management, and snapshot lifetime management on the JS side every time is painful.
So I make Rust own the hot paths and process orchestration, while Node gets a convenient surface.</p>
<p>The direction I am taking with <code>corsa-oxlint</code> fits this idea well.
Rule authors write rules in JS / TS.
But retrieving type information and talking to the <code>tsgo</code> worker is handled by a Rust-backed layer.</p>
<p>Here too, the subject for me is not just &quot;bindings.&quot;
The subject is <strong>how to distribute the language processor as a shared resource</strong>.</p>
<h2>From One Compiler to Many Consumers</h2>
<p>With a traditional CLI, the relationship between compiler and consumer tends to be one-to-one.</p>
<pre><code class="language-text">tsc command
  -&gt; compiler
  -&gt; diagnostics
</code></pre>
<p>But the actual shape of editors and toolchains is closer to many-to-one.</p>
<pre><code class="language-text">editor hover
editor completion
lint rule
code action
AI agent
test runner
build watcher
  -&gt; shared language-processing state
</code></pre>
<p>Of course, this does not mean everything should be truly concentrated into one process.
Fault isolation matters, and it may be better to separate workers depending on the nature of the query.
Whether read-heavy type queries and workflows that aggressively rewrite virtual documents should live in the same session is a question to treat carefully.</p>
<p>That is exactly why an orchestrator is needed.</p>
<p>An orchestrator is not a wrapper that hides the compiler.
It is a control plane for arranging multiple consumers around the compiler.</p>
<p>&quot;Control plane&quot; may sound a little grand, but the work is similar.
Which profile should create a worker?
Which worker is warm?
Which snapshot is still usable?
Which request is stuck?
Which transport fits this workflow?
Which failures should be retried, and which should be returned to the caller?</p>
<p>These decisions live in a layer separate from the compiler engine itself.</p>
<h2>Language Processors Will Stay Resident</h2>
<p>I think language processors will increasingly be treated as resident processes.</p>
<p>The reason is simple: developer experience is moving from batch toward conversation.</p>
<p>It is not only that a human saves a file and the project is checked once.
The editor offers suggestions while text is being entered.
The linter runs rules while looking at the AST and types.
An AI agent asks small questions like &quot;what is the type of this node?&quot;, &quot;where does this symbol come from?&quot;, and &quot;did this fix remove the diagnostic?&quot;
Frameworks generate virtual files and map them back to the source files.</p>
<p>In that world, a language processor is less a command you start every time and more a service that keeps receiving questions.</p>
<p>And if it becomes a service, it needs orchestration.
It needs worker pools, caches, snapshots, backpressure, timeouts, observability, graceful shutdown, version pinning, and compatibility policy.</p>
<p>I am building that layer in <code>corsa-bind</code> through the concrete target of <code>tsgo</code>.</p>
<p>A language processor does not end at the parser and checker.
Keeping it alive, sharing it, folding it down when it breaks, and handing it to author experiences in other languages are all part of the future of language tooling.</p>
<h2>Conclusion</h2>
<p><code>tsgo</code> is a faster <code>tsc</code>.
But stopping there would be a little wasteful.</p>
<p>Seen through its stdio API, <code>tsgo</code> is also a stateful language processor server.
And what I am building in <code>corsa-bind</code> is an orchestration layer for using that server from Rust, Node, and multiple native bindings.</p>
<p>The important point for me is not to forcefully absorb the compiler engine itself.
Respect upstream <code>tsgo</code>, do not fork it, do not patch it, and instead reuse sessions, manage snapshots, choose transports, and coordinate workers from the outside.</p>
<p>This is close to the way we think about distributed servers.
There are processes, messages, handles to remote state, lifetimes, failures, observations, and cleanup.</p>
<p>Do not only call the language processor as a library.
Operate it as a service.</p>
<p>That is the language processor orchestration I am trying to build in <code>corsa-bind</code>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Tech Behind Transparent Watercolor on the Web</title>
      <link>https://wtrclred.vercel.app/posts/16</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/16</guid>
      <description>How this site&apos;s watercolor background is split into paper, wet pigment, dry pigment, fallbacks, and hydration boundaries.</description>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-27T11:12:35.000Z</dc:date>
      <category>web-graphics</category>
      <category>canvas</category>
      <category>webgpu</category>
      <category>webgl</category>
      <category>performance</category>
      <content:encoded><![CDATA[<blockquote>
<p>This article was generated 100% by AI, using GPT-5.5.</p>
</blockquote>
<h2>Introduction</h2>
<p>This site has a faint transparent-watercolor background behind the writing.
It looks like a quiet decoration, but it is not a single image.
The current implementation draws paper with WebGPU, moves wet pigment a little with WebGL2, keeps a Canvas 2D fallback, and switches to a static background on mobile.</p>
<p>The important point is that this is not an attempt to build a physically correct watercolor simulator in the browser.
For a writing site, that would be the wrong goal.
The background should not become more important than the text.</p>
<p>The effect only needs a few cues.</p>
<ul>
<li><p>the paper has relief and fiber</p>
</li>
<li><p>pigment is not just a uniform translucent color</p>
</li>
<li><p>wet pigment moves for a short time</p>
</li>
<li><p>dry pigment stays fixed to the paper</p>
</li>
<li><p>environments that cannot run the effect fail quietly</p>
</li>
<li><p>text, links, and scrolling remain untouched</p>
</li>
</ul>
<p>After rereading the implementation, the watercolor feeling comes less from one spectacular shader and more from this split.
Paper, wet paint, dry paint, input, and fallbacks each have their own boundary.
That boundary is the design.</p>
<h2>The Background Stays Weak</h2>
<p>The visual stack is roughly this.</p>
<pre><code class="language-text">paper fallback span
paper fallback canvas
paper WebGPU canvas
brush canvas
page content
controls
</code></pre>
<p>The Vue template mostly just declares the layers.</p>
<pre><code class="language-vue">&lt;div class=&quot;watercolor-bg-container&quot; aria-hidden=&quot;true&quot;&gt;
  &lt;span ref=&quot;fallbackLayerElement&quot; class=&quot;paper-fallback&quot; /&gt;
  &lt;canvas ref=&quot;fallbackCanvasElement&quot; class=&quot;paper-fallback-canvas&quot; aria-hidden=&quot;true&quot; /&gt;
  &lt;canvas ref=&quot;paperCanvasElement&quot; class=&quot;paper-canvas&quot; aria-hidden=&quot;true&quot; /&gt;
  &lt;canvas ref=&quot;brushCanvasElement&quot; class=&quot;brush-canvas&quot; aria-hidden=&quot;true&quot; /&gt;
&lt;/div&gt;
</code></pre>
<p>The whole thing is <code>aria-hidden</code> because it is not content.
It is material behind the content.</p>
<p>The container itself is not a giant fixed overlay.
The canvases are fixed, while the container stays out of the content flow.</p>
<pre><code class="language-css">.watercolor-bg-container {
  position: static;
  pointer-events: auto;
  background: transparent;
}

.paper-fallback-canvas,
.paper-canvas,
.brush-canvas {
  position: fixed;
  inset: 0;
  pointer-events: none;
}
</code></pre>
<p><code>pointer-events: none</code> matters.
The background covers the screen visually, but it cannot steal links or scrolling.
It is visually large and behaviorally weak.
That is where a writing-site decoration should be.</p>
<h2>Do Not Wake Every Island</h2>
<p>The background is a Vuerend island, but it does not hydrate on every viewport.</p>
<pre><code class="language-ts">const desktopIslandMedia = &quot;(min-width: 769px)&quot;;

export const WatercolorBackgroundIsland = defineIsland(&quot;watercolor-background&quot;, {
  component: WatercolorBackgroundIslandView,
  load: () =&gt; import(&quot;./features/ShellWatercolorBackgroundIslandLoader&quot;),
  hydrate: &quot;media&quot;,
  media: desktopIslandMedia,
});
</code></pre>
<p>Only desktop-width screens load the client code.
On mobile, CSS hides the canvases and uses <code>bg-sp.png</code>.</p>
<pre><code class="language-css">@media (max-width: 768px) {
  html {
    background: #f1eedf url(&quot;/bg-sp.png&quot;) center center / cover no-repeat;
  }

  .paper-fallback {
    opacity: 1 !important;
    background: #f1eedf url(&quot;/bg-sp.png&quot;) center center / cover no-repeat;
  }

  .paper-fallback-canvas,
  .paper-canvas,
  .brush-canvas {
    display: none;
  }
}
</code></pre>
<p>That is not just a compromise.
It is part of the background contract.
On touch-first screens, interactive watercolor has more risk than value.
Static paper is enough.</p>
<p>Pointer input is gated the same way.</p>
<pre><code class="language-ts">export function canUseFinePointerPaint(): boolean {
  return window.matchMedia(&quot;(hover: hover) and (pointer: fine)&quot;).matches;
}
</code></pre>
<p>The implementation decides not only what it can do, but where it should not run.</p>
<h2>Draw Paper First</h2>
<p>The WebGPU paper shader initializes asynchronously.
Before that path finishes, a Canvas 2D fallback paper is drawn.</p>
<pre><code class="language-ts">renderPaperFallback(fallbackCanvasElement.value, maxDpr);
brushLayer.resize();
removeViewportListeners = addShellViewportListeners(queueResize);
</code></pre>
<p>The fallback is not a flat color.
It layers radial wash, shadow patches, highlight patches, fibers, and a grain tile.</p>
<pre><code class="language-ts">context.fillStyle = &quot;#f1eedf&quot;;
context.fillRect(0, 0, width, height);
drawRadialWash(context, width, height);

context.globalCompositeOperation = &quot;multiply&quot;;
drawPaperPatches(context, width, height, random, Math.round(820 * areaScale), &quot;shadow&quot;);

context.globalCompositeOperation = &quot;screen&quot;;
drawPaperPatches(context, width, height, random, Math.round(520 * areaScale), &quot;highlight&quot;);
</code></pre>
<p>When WebGPU succeeds, the fallback opacity is reduced.
It is not removed entirely.</p>
<pre><code class="language-ts">target.fallbackLayer?.style.setProperty(&quot;opacity&quot;, &quot;0.1&quot;);
target.fallbackCanvas?.style.setProperty(&quot;opacity&quot;, &quot;0.16&quot;);
</code></pre>
<p>This order matters.
If WebGPU is fast, the shader paper appears.
If it is slow, fallback paper is already visible.
If it fails, paper remains.
That is a good failure mode for a background.</p>
<h2>Bake One Paper Layer With WebGPU</h2>
<p>The WebGPU path imports <code>ShellSitePaper.wgsl</code> as raw shader source.</p>
<pre><code class="language-ts">import sitePaperShaderSource from &quot;./ShellSitePaper.wgsl?raw&quot;;

export const sitePaperShader = sitePaperShaderSource;
</code></pre>
<p>The adapter asks for <code>low-power</code>.</p>
<pre><code class="language-ts">const adapter = await gpu.requestAdapter({ powerPreference: &quot;low-power&quot; });
</code></pre>
<p>Drawing paper behind text does not need the strongest possible GPU.
The render itself is a single fullscreen triangle.</p>
<pre><code class="language-wgsl">var positions = array&lt;vec2f, 3&gt;(
  vec2f(-1.0, -1.0),
  vec2f(3.0, -1.0),
  vec2f(-1.0, 3.0)
);
</code></pre>
<p>DPR is capped at <code>1.25</code>.</p>
<pre><code class="language-ts">const dpr = Math.min(window.devicePixelRatio || 1, maxDpr);
</code></pre>
<p>The paper grain does not need native DPR to read as paper.
The useful resolution for a background is not the same as the physical display resolution.</p>
<h2>Keep The Paper Still</h2>
<p>The paper shader receives no time uniform.
Its coordinates come from viewport shape and seed.</p>
<pre><code class="language-wgsl">let aspect = uniforms.resolution.x / max(uniforms.resolution.y, 1.0);
let centered = (input.uv - 0.5) * vec2f(aspect * 1.72, 1.72);
let point =
  rotate(centered * vec2f(1.0, 1.04), -0.06)
  + vec2f(uniforms.seed * 6.4, uniforms.seed * 3.2);
</code></pre>
<p>If the paper moves while someone reads, it stops feeling like paper and becomes an effect.
For a background, stillness is stronger.</p>
<p>The shader is also not just color noise.
It builds a height field from material roles like <code>embossField</code>, <code>microGrainField</code>, <code>fiberField</code>, <code>celluloseNetworkField</code>, <code>coldPressToothField</code>, <code>crumpleField</code>, and <code>wrinkleLineField</code>.
Those fields are mixed into <code>paperHeight</code>.</p>
<pre><code class="language-wgsl">return macroRelief * 0.34
  + detail.x * 2.16
  + micro * (0.76 + uniforms.fiberDensity * 0.16 + uniforms.grainScale * 0.1)
  + (emboss - 0.18) * (0.18 + uniforms.embossDepth * 0.24)
  + (tooth.y - 0.28) * (0.46 + uniforms.embossDepth * 0.34)
  - (tooth.x - 0.18) * (0.34 + uniforms.shadowStrength * 0.22)
  + (grain - 0.5) * 0.018
  + (pulp - 0.3) * 0.018;
</code></pre>
<p><code>tooth.y</code> acts like raised paper.
<code>tooth.x</code> acts like a cavity.
That sign difference matters because pigment needs a surface to settle into.
The paper is not a white backdrop; it is terrain.</p>
<p>The paper shader can be read as this algorithm.</p>
<pre><code class="language-text">1. Convert uv into paper coordinates
2. Build low-frequency macro relief
3. Build fiber / fibril / cellulose / tooth / pulp fields separately
4. Mix those fields into a height field
5. Sample the height field nearby to produce a normal
6. Build cavity and ridge masks from the normal, relief, and tooth fields
7. Mix cavity shadow and ridge highlight into a warm white paper color
</code></pre>
<p>The normal is not analytic.
It is derived by sampling <code>paperHeight</code> at small offsets.</p>
<pre><code class="language-wgsl">fn paperNormal(point: vec2f) -&gt; vec3f {
  let epsilon = 0.00076;
  let center = paperHeight(point);
  let dx = paperHeight(point + vec2f(epsilon, 0.0)) - center;
  let dy = paperHeight(point + vec2f(0.0, epsilon)) - center;
  let strength = 3.1 + uniforms.embossDepth * 2.2 + uniforms.grainScale * 1.1;
  return normalize(vec3f(-dx * strength, -dy * strength, 1.0));
}
</code></pre>
<p>That normal and paper tooth are then split into low spots and raised ridges.</p>
<pre><code class="language-wgsl">let cavityMask = saturate(
  max(0.0, -localRelief * 1.84)
    + tooth.x * 0.68
    + (1.0 - normal.z) * 0.4
    + max(0.0, -diffuse) * 0.16
    + crumple.x * 0.08
    + wrinkleLines.x * 0.05
);

let ridgeMask = saturate(
  max(0.0, localRelief * 1.42)
    + tooth.y * 0.62
    + detail.y * 0.06
    + max(0.0, diffuse) * 0.18
    + crumple.y * 0.06
    + wrinkleLines.y * 0.04
);
</code></pre>
<p>The cavity shadow is intentionally limited.
This is still a paper background, so relief should not steal text contrast.
The final tone is clamped with <code>clamp(tone, vec3f(0.72), vec3f(1.0))</code> to keep it from sinking too far.</p>
<h2>The Brush Is Wet State, Not A Stamp</h2>
<p>One tempting path is to draw organic Canvas 2D shapes and call it watercolor.
The fallback brush does use that kind of drawing.
But by itself, it still feels like translucent images being stamped onto the page.
Watercolor needs a little time after the stroke.</p>
<p>The normal path uses a small WebGL2 solver, but the solver is created lazily.</p>
<pre><code class="language-ts">const fallback = createBrushLayer(options);
let engine: FluidBrushEngine | undefined;
let triedEngine = false;

function ensureEngine(): FluidBrushEngine | undefined {
  if (engine || triedEngine) {
    return engine;
  }

  triedEngine = true;
  engine = FluidBrushEngine.create();
  return engine;
}
</code></pre>
<p>If WebGL2 or float framebuffer support is missing, the Canvas 2D brush is used instead.</p>
<pre><code class="language-ts">if (!gl || !gl.getExtension(&quot;EXT_color_buffer_float&quot;)) {
  return undefined;
}
</code></pre>
<p>The solver does not run at full viewport resolution.
Its base simulation size is <code>560</code>.</p>
<pre><code class="language-ts">const simulationBaseSize = 560;
</code></pre>
<p>For background bleeding, a slightly soft field is better than a precise high-resolution simulation.
Lower resolution helps both performance and appearance.</p>
<p>The fluid section can be read as one sentence first.</p>
<pre><code class="language-text">the brush deposits wet -&gt; velocity carries wet -&gt; pressure calms the flow -&gt; old wet becomes dry
</code></pre>
<p>The thing to visually follow is only the blue <code>wet</code> stain.
The <code>velocity</code> arrows move that stain, red <code>pressure</code> is the brake that keeps the flow from bursting, and orange <code>dry</code> is the pigment fixed into the paper.
This way of reading the solver was inspired by <a href="https://x.com/threejs/status/2047490608940655073?s=20" target="_blank" rel="noopener noreferrer">this three.js post on X</a>, then reframed around this site&#39;s own watercolor background implementation.</p>
<p>One frame of the brush solver looks roughly like this.</p>
<pre><code class="language-text">curl
vorticity confinement
advect velocity
boundary velocity
advect wet
boundary wet
dry wet into dry
remove dried wet
divergence
pressure solve
subtract pressure gradient
boundary velocity
display
</code></pre>
<p>&lt;div&gt;
&lt;svg viewBox=&quot;0 0 760 360&quot; role=&quot;img&quot; aria-labelledby=&quot;fluid-frame-title-en&quot;&gt;
  &lt;title id=&quot;fluid-frame-title-en&quot;&gt;One frame of the WebGL2 brush solver&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arrow-fluid-frame-en&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;8.5&quot; refY=&quot;5&quot; markerWidth=&quot;5&quot; markerHeight=&quot;5&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; fill=&quot;#35495e&quot; /&gt;
    &lt;/marker&gt;
    &lt;linearGradient id=&quot;code-wash-fluid-frame-en&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0.045&quot; /&gt;
      &lt;stop offset=&quot;72%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;760&quot; height=&quot;360&quot; fill=&quot;url(#code-wash-fluid-frame-en)&quot; /&gt;
  &lt;path d=&quot;M 0 0.5 H 760 M 0 359.5 H 760&quot; stroke=&quot;#2f3b47&quot; stroke-opacity=&quot;0.16&quot; /&gt;
  &lt;text x=&quot;48&quot; y=&quot;62&quot; font-size=&quot;18&quot; font-weight=&quot;700&quot; fill=&quot;#35495e&quot;&gt;One Frame At A Glance&lt;/text&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;14&quot; text-anchor=&quot;middle&quot;&gt;
    &lt;ellipse cx=&quot;112&quot; cy=&quot;119&quot; rx=&quot;70&quot; ry=&quot;32&quot; fill=&quot;#64d4a3&quot; opacity=&quot;0.16&quot; /&gt;
    &lt;text x=&quot;112&quot; y=&quot;115&quot; fill=&quot;#35495e&quot;&gt;curl&lt;/text&gt;
    &lt;text x=&quot;112&quot; y=&quot;133&quot; fill=&quot;#666&quot;&gt;measure spin&lt;/text&gt;
    &lt;line x1=&quot;184&quot; y1=&quot;119&quot; x2=&quot;204&quot; y2=&quot;119&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;281&quot; cy=&quot;119&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#64d4a3&quot; opacity=&quot;0.16&quot; /&gt;
    &lt;text x=&quot;281&quot; y=&quot;115&quot; fill=&quot;#35495e&quot;&gt;vorticity&lt;/text&gt;
    &lt;text x=&quot;281&quot; y=&quot;133&quot; fill=&quot;#666&quot;&gt;restore swirl&lt;/text&gt;
    &lt;line x1=&quot;358&quot; y1=&quot;119&quot; x2=&quot;382&quot; y2=&quot;119&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;455&quot; cy=&quot;119&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#646cff&quot; opacity=&quot;0.13&quot; /&gt;
    &lt;text x=&quot;455&quot; y=&quot;115&quot; fill=&quot;#35495e&quot;&gt;advect u&lt;/text&gt;
    &lt;text x=&quot;455&quot; y=&quot;133&quot; fill=&quot;#666&quot;&gt;move velocity&lt;/text&gt;
    &lt;line x1=&quot;532&quot; y1=&quot;119&quot; x2=&quot;556&quot; y2=&quot;119&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;629&quot; cy=&quot;119&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#646cff&quot; opacity=&quot;0.13&quot; /&gt;
    &lt;text x=&quot;629&quot; y=&quot;115&quot; fill=&quot;#35495e&quot;&gt;advect wet&lt;/text&gt;
    &lt;text x=&quot;629&quot; y=&quot;133&quot; fill=&quot;#666&quot;&gt;move pigment&lt;/text&gt;
    &lt;line x1=&quot;629&quot; y1=&quot;154&quot; x2=&quot;629&quot; y2=&quot;184&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;629&quot; cy=&quot;219&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#f59e0b&quot; opacity=&quot;0.14&quot; /&gt;
    &lt;text x=&quot;629&quot; y=&quot;215&quot; fill=&quot;#35495e&quot;&gt;dry&lt;/text&gt;
    &lt;text x=&quot;629&quot; y=&quot;233&quot; fill=&quot;#666&quot;&gt;wet into dry&lt;/text&gt;
    &lt;line x1=&quot;552&quot; y1=&quot;219&quot; x2=&quot;530&quot; y2=&quot;219&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;455&quot; cy=&quot;219&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;455&quot; y=&quot;215&quot; fill=&quot;#35495e&quot;&gt;divergence&lt;/text&gt;
    &lt;text x=&quot;455&quot; y=&quot;233&quot; fill=&quot;#666&quot;&gt;measure expansion&lt;/text&gt;
    &lt;line x1=&quot;380&quot; y1=&quot;219&quot; x2=&quot;358&quot; y2=&quot;219&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;281&quot; cy=&quot;219&quot; rx=&quot;76&quot; ry=&quot;32&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;281&quot; y=&quot;215&quot; fill=&quot;#35495e&quot;&gt;pressure&lt;/text&gt;
    &lt;text x=&quot;281&quot; y=&quot;233&quot; fill=&quot;#666&quot;&gt;8 iterations&lt;/text&gt;
    &lt;line x1=&quot;204&quot; y1=&quot;219&quot; x2=&quot;184&quot; y2=&quot;219&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;ellipse cx=&quot;112&quot; cy=&quot;219&quot; rx=&quot;70&quot; ry=&quot;32&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;112&quot; y=&quot;215&quot; fill=&quot;#35495e&quot;&gt;gradient&lt;/text&gt;
    &lt;text x=&quot;112&quot; y=&quot;233&quot; fill=&quot;#666&quot;&gt;subtract pressure&lt;/text&gt;
    &lt;line x1=&quot;112&quot; y1=&quot;252&quot; x2=&quot;112&quot; y2=&quot;282&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-frame-en)&quot; /&gt;
    &lt;path d=&quot;M 60 302 C 200 292 388 292 692 302&quot; fill=&quot;none&quot; stroke=&quot;#fcb32c&quot; stroke-width=&quot;28&quot; stroke-linecap=&quot;round&quot; opacity=&quot;0.14&quot; /&gt;
    &lt;text x=&quot;373&quot; y=&quot;307&quot; fill=&quot;#35495e&quot;&gt;display: composite wet / dry back into transparent brush canvas&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<p>This is not a complete Navier-Stokes solver.
It adds a little vorticity back into the velocity field, moves wet pigment with semi-Lagrangian advection, and uses pressure projection to reduce divergence.
For a background, that stable-fluids subset is enough.</p>
<h2>First, There Are No Particles</h2>
<p>When people hear &quot;fluid,&quot; it is easy to imagine many little water particles moving around.
This implementation does not track particles.
It creates a texture grid smaller than the screen, and each pixel stores &quot;which way water is moving here&quot; and &quot;how much wet pigment is here.&quot;</p>
<p>So the thing being simulated is a field, not a set of particles.</p>
<pre><code class="language-text">Each pixel:
  velocity = water direction at this position
  wet      = wet pigment at this position
  dry      = dried pigment at this position
</code></pre>
<p>One stroke changes roughly like this.</p>
<p>&lt;div&gt;
&lt;svg viewBox=&quot;0 0 760 520&quot; role=&quot;img&quot; aria-labelledby=&quot;toy-fluid-title-en&quot;&gt;
  &lt;title id=&quot;toy-fluid-title-en&quot;&gt;How one stroke separates into wet, velocity, and dry fields&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arrow-toy-fluid-en&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;8.5&quot; refY=&quot;5&quot; markerWidth=&quot;5&quot; markerHeight=&quot;5&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; fill=&quot;#35495e&quot; /&gt;
    &lt;/marker&gt;
    &lt;radialGradient id=&quot;wet-blob-en&quot; cx=&quot;50%&quot; cy=&quot;50%&quot; r=&quot;60%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#646cff&quot; stop-opacity=&quot;0.46&quot; /&gt;
      &lt;stop offset=&quot;70%&quot; stop-color=&quot;#646cff&quot; stop-opacity=&quot;0.18&quot; /&gt;
      &lt;stop offset=&quot;100%&quot; stop-color=&quot;#646cff&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/radialGradient&gt;
    &lt;radialGradient id=&quot;dry-blob-en&quot; cx=&quot;50%&quot; cy=&quot;50%&quot; r=&quot;60%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#f59e0b&quot; stop-opacity=&quot;0.42&quot; /&gt;
      &lt;stop offset=&quot;80%&quot; stop-color=&quot;#f59e0b&quot; stop-opacity=&quot;0.16&quot; /&gt;
      &lt;stop offset=&quot;100%&quot; stop-color=&quot;#f59e0b&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/radialGradient&gt;
    &lt;linearGradient id=&quot;code-wash-toy-fluid-en&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0.045&quot; /&gt;
      &lt;stop offset=&quot;72%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;760&quot; height=&quot;520&quot; fill=&quot;url(#code-wash-toy-fluid-en)&quot; /&gt;
  &lt;path d=&quot;M 0 0.5 H 760 M 0 519.5 H 760&quot; stroke=&quot;#2f3b47&quot; stroke-opacity=&quot;0.16&quot; /&gt;
  &lt;text x=&quot;48&quot; y=&quot;62&quot; font-size=&quot;18&quot; font-weight=&quot;700&quot; fill=&quot;#35495e&quot;&gt;Blue wet moves, orange dry remains&lt;/text&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;14&quot;&gt;
    &lt;path d=&quot;M 380 92 L 380 442 M 58 266 L 702 266&quot; stroke=&quot;#ddd&quot; stroke-width=&quot;1&quot; opacity=&quot;0.5&quot; /&gt;
    &lt;text x=&quot;80&quot; y=&quot;120&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;0. splat&lt;/text&gt;
    &lt;text x=&quot;80&quot; y=&quot;142&quot; fill=&quot;#666&quot;&gt;add wet pigment and velocity&lt;/text&gt;
    &lt;ellipse cx=&quot;210&quot; cy=&quot;180&quot; rx=&quot;46&quot; ry=&quot;30&quot; fill=&quot;url(#wet-blob-en)&quot; /&gt;
    &lt;circle cx=&quot;210&quot; cy=&quot;180&quot; r=&quot;7&quot; fill=&quot;#646cff&quot; /&gt;
    &lt;path d=&quot;M 210 180 L 264 160&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 210 180 L 260 202&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 210 180 L 174 208&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;</p>
<p>&lt;text x=&quot;424&quot; y=&quot;120&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;1. advect&lt;/text&gt;
    &lt;text x=&quot;424&quot; y=&quot;142&quot; fill=&quot;#666&quot;&gt;velocity carries wet pigment&lt;/text&gt;
    &lt;ellipse cx=&quot;534&quot; cy=&quot;182&quot; rx=&quot;58&quot; ry=&quot;28&quot; transform=&quot;rotate(10 534 182)&quot; fill=&quot;url(#wet-blob-en)&quot; /&gt;
    &lt;path d=&quot;M 478 176 C 520 158 564 164 610 188&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 482 208 C 528 194 574 198 616 220&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;</p>
<p>&lt;text x=&quot;80&quot; y=&quot;314&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;2. project&lt;/text&gt;
    &lt;text x=&quot;80&quot; y=&quot;336&quot; fill=&quot;#666&quot;&gt;pressure keeps the flow from exploding&lt;/text&gt;
    &lt;circle cx=&quot;204&quot; cy=&quot;390&quot; r=&quot;36&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.12&quot; /&gt;
    &lt;path d=&quot;M 204 390 L 204 354&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 204 390 L 242 390&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 204 390 L 204 426&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 204 390 L 166 390&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;path d=&quot;M 292 362 C 326 380 326 416 292 438&quot; fill=&quot;none&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;3&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;text x=&quot;80&quot; y=&quot;450&quot; fill=&quot;#666&quot;&gt;outward burst is softened&lt;/text&gt;</p>
<p>&lt;text x=&quot;424&quot; y=&quot;314&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;3. dry&lt;/text&gt;
    &lt;text x=&quot;424&quot; y=&quot;336&quot; fill=&quot;#666&quot;&gt;wet fades, dry remains on paper&lt;/text&gt;
    &lt;ellipse cx=&quot;540&quot; cy=&quot;382&quot; rx=&quot;62&quot; ry=&quot;30&quot; transform=&quot;rotate(8 540 382)&quot; fill=&quot;url(#dry-blob-en)&quot; /&gt;
    &lt;ellipse cx=&quot;566&quot; cy=&quot;370&quot; rx=&quot;36&quot; ry=&quot;18&quot; transform=&quot;rotate(8 566 370)&quot; fill=&quot;url(#wet-blob-en)&quot; opacity=&quot;0.55&quot; /&gt;
    &lt;path d=&quot;M 494 402 C 528 416 576 412 616 392&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-width=&quot;3&quot; opacity=&quot;0.7&quot; /&gt;
    &lt;text x=&quot;424&quot; y=&quot;430&quot; fill=&quot;#666&quot;&gt;dry pigment no longer advects&lt;/text&gt;
  &lt;/g&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;13&quot;&gt;
    &lt;circle cx=&quot;78&quot; cy=&quot;470&quot; r=&quot;7&quot; fill=&quot;#646cff&quot; opacity=&quot;0.55&quot; /&gt;
    &lt;text x=&quot;94&quot; y=&quot;475&quot; fill=&quot;#35495e&quot;&gt;blue = moving wet&lt;/text&gt;
    &lt;line x1=&quot;222&quot; y1=&quot;470&quot; x2=&quot;260&quot; y2=&quot;470&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-toy-fluid-en)&quot; /&gt;
    &lt;text x=&quot;272&quot; y=&quot;475&quot; fill=&quot;#35495e&quot;&gt;arrows = velocity&lt;/text&gt;
    &lt;circle cx=&quot;406&quot; cy=&quot;470&quot; r=&quot;7&quot; fill=&quot;#f59e0b&quot; opacity=&quot;0.6&quot; /&gt;
    &lt;text x=&quot;422&quot; y=&quot;475&quot; fill=&quot;#35495e&quot;&gt;orange = fixed dry&lt;/text&gt;
    &lt;circle cx=&quot;574&quot; cy=&quot;470&quot; r=&quot;7&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.45&quot; /&gt;
    &lt;text x=&quot;590&quot; y=&quot;475&quot; fill=&quot;#35495e&quot;&gt;red = pressure correction&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<p>In this picture, the solver mostly moves <code>velocity</code> and <code>wet</code> every frame.
<code>dry</code> is removed from that moving system.
That is why pigment first rides the water, then gradually becomes fixed into the paper.</p>
<h2>The Fluid Fields Are Textures</h2>
<p>The solver does not keep arrays on the CPU.
It keeps fields as WebGL2 <code>RGBA16F</code> textures.
Each pass draws one fullscreen triangle, and the fragment shader writes the next texture.
Because a pass cannot safely read and write the same texture, <code>velocity</code>, <code>wet</code>, <code>dry</code>, and <code>pressure</code> are ping-pong buffers with read and write sides.</p>
<pre><code class="language-text">velocity.xy   velocity
velocity.zw   unused / spare alpha
wet.rgb       absorption from wet pigment
wet.a         wet amount
dry.rgb       absorption from dried pigment
dry.a         dried amount
pressure.x    pressure
divergence.x  velocity divergence
curl.x        2D curl
</code></pre>
<p>&lt;div&gt;
&lt;svg viewBox=&quot;0 0 760 340&quot; role=&quot;img&quot; aria-labelledby=&quot;fluid-textures-title-en&quot;&gt;
  &lt;title id=&quot;fluid-textures-title-en&quot;&gt;Texture fields owned by the fluid solver&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arrow-fluid-textures-en&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;8.5&quot; refY=&quot;5&quot; markerWidth=&quot;5&quot; markerHeight=&quot;5&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; fill=&quot;#35495e&quot; /&gt;
    &lt;/marker&gt;
    &lt;linearGradient id=&quot;code-wash-fluid-textures-en&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0.045&quot; /&gt;
      &lt;stop offset=&quot;72%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;760&quot; height=&quot;340&quot; fill=&quot;url(#code-wash-fluid-textures-en)&quot; /&gt;
  &lt;path d=&quot;M 0 0.5 H 760 M 0 339.5 H 760&quot; stroke=&quot;#2f3b47&quot; stroke-opacity=&quot;0.16&quot; /&gt;
  &lt;text x=&quot;48&quot; y=&quot;62&quot; font-size=&quot;18&quot; font-weight=&quot;700&quot; fill=&quot;#35495e&quot;&gt;The Fields Live As Textures&lt;/text&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;14&quot; text-anchor=&quot;middle&quot;&gt;
    &lt;ellipse cx=&quot;124&quot; cy=&quot;130&quot; rx=&quot;78&quot; ry=&quot;42&quot; fill=&quot;#646cff&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;124&quot; y=&quot;121&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;velocity&lt;/text&gt;
    &lt;text x=&quot;124&quot; y=&quot;143&quot; fill=&quot;#666&quot;&gt;xy = flow&lt;/text&gt;
    &lt;ellipse cx=&quot;290&quot; cy=&quot;130&quot; rx=&quot;78&quot; ry=&quot;42&quot; fill=&quot;#42b883&quot; opacity=&quot;0.12&quot; /&gt;
    &lt;text x=&quot;290&quot; y=&quot;121&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;wet&lt;/text&gt;
    &lt;text x=&quot;290&quot; y=&quot;143&quot; fill=&quot;#666&quot;&gt;rgb = wet pigment&lt;/text&gt;
    &lt;ellipse cx=&quot;456&quot; cy=&quot;130&quot; rx=&quot;78&quot; ry=&quot;42&quot; fill=&quot;#f59e0b&quot; opacity=&quot;0.12&quot; /&gt;
    &lt;text x=&quot;456&quot; y=&quot;121&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;dry&lt;/text&gt;
    &lt;text x=&quot;456&quot; y=&quot;143&quot; fill=&quot;#666&quot;&gt;rgb = dry pigment&lt;/text&gt;
    &lt;ellipse cx=&quot;622&quot; cy=&quot;130&quot; rx=&quot;78&quot; ry=&quot;42&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;622&quot; y=&quot;121&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;pressure&lt;/text&gt;
    &lt;text x=&quot;622&quot; y=&quot;143&quot; fill=&quot;#666&quot;&gt;x = pressure&lt;/text&gt;
    &lt;text x=&quot;196&quot; y=&quot;232&quot; fill=&quot;#35495e&quot;&gt;read texture&lt;/text&gt;
    &lt;path d=&quot;M 128 244 C 162 250 230 250 264 244&quot; fill=&quot;none&quot; stroke=&quot;#8899aa&quot; stroke-width=&quot;2&quot; opacity=&quot;0.55&quot; /&gt;
    &lt;text x=&quot;380&quot; y=&quot;208&quot; fill=&quot;#666&quot; font-size=&quot;13&quot;&gt;pass writes the next field&lt;/text&gt;
    &lt;line x1=&quot;282&quot; y1=&quot;224&quot; x2=&quot;476&quot; y2=&quot;224&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-textures-en)&quot; /&gt;
    &lt;text x=&quot;564&quot; y=&quot;232&quot; fill=&quot;#35495e&quot;&gt;write texture&lt;/text&gt;
    &lt;path d=&quot;M 496 244 C 530 250 598 250 632 244&quot; fill=&quot;none&quot; stroke=&quot;#8899aa&quot; stroke-width=&quot;2&quot; opacity=&quot;0.55&quot; /&gt;
    &lt;path d=&quot;M 564 260 C 520 306 240 306 196 260&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-fluid-textures-en)&quot; /&gt;
    &lt;text x=&quot;380&quot; y=&quot;316&quot; fill=&quot;#666&quot;&gt;swap: write becomes the next read&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<p>As a fluid algorithm, one frame is roughly this.</p>
<pre><code class="language-text">u = velocity
w = wet pigment
d = dry pigment

u &lt;- add vorticity(u)
u &lt;- advect(u, u)
w &lt;- advect(w, u)
d, w &lt;- dry(w, d)
u &lt;- project(u)
</code></pre>
<p><code>dry</code> is not advected.
It represents pigment fixed into paper.
The watercolor feeling comes as much from deciding what cannot move as from moving the wet field.</p>
<h2>Advection Samples Backward</h2>
<p>Advection uses a semi-Lagrangian step.
The amount that arrives at the current pixel is assumed to have been upstream one step earlier.
So the shader samples backward from <code>vUv</code>.</p>
<p>&lt;div&gt;
&lt;svg viewBox=&quot;0 0 760 320&quot; role=&quot;img&quot; aria-labelledby=&quot;advection-title-en&quot;&gt;
  &lt;title id=&quot;advection-title-en&quot;&gt;Backtracing in semi-Lagrangian advection&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arrow-advection-en&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;8.5&quot; refY=&quot;5&quot; markerWidth=&quot;5&quot; markerHeight=&quot;5&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; fill=&quot;#35495e&quot; /&gt;
    &lt;/marker&gt;
    &lt;linearGradient id=&quot;code-wash-advection-en&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0.045&quot; /&gt;
      &lt;stop offset=&quot;72%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;760&quot; height=&quot;320&quot; fill=&quot;url(#code-wash-advection-en)&quot; /&gt;
  &lt;path d=&quot;M 0 0.5 H 760 M 0 319.5 H 760&quot; stroke=&quot;#2f3b47&quot; stroke-opacity=&quot;0.16&quot; /&gt;
  &lt;text x=&quot;48&quot; y=&quot;62&quot; font-size=&quot;18&quot; font-weight=&quot;700&quot; fill=&quot;#35495e&quot;&gt;Advection Reads Upstream&lt;/text&gt;
  &lt;g stroke=&quot;#ddd&quot; stroke-width=&quot;1&quot;&gt;
    &lt;path d=&quot;M 84 88 L 84 256 M 144 88 L 144 256 M 204 88 L 204 256 M 264 88 L 264 256 M 324 88 L 324 256 M 384 88 L 384 256 M 444 88 L 444 256 M 504 88 L 504 256 M 564 88 L 564 256 M 624 88 L 624 256&quot; /&gt;
    &lt;path d=&quot;M 64 108 L 660 108 M 64 148 L 660 148 M 64 188 L 660 188 M 64 228 L 660 228&quot; /&gt;
  &lt;/g&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;14&quot;&gt;
    &lt;text x=&quot;324&quot; y=&quot;122&quot; fill=&quot;#35495e&quot; text-anchor=&quot;middle&quot;&gt;previous sample position&lt;/text&gt;
    &lt;circle cx=&quot;324&quot; cy=&quot;150&quot; r=&quot;8&quot; fill=&quot;#42b883&quot; /&gt;
    &lt;text x=&quot;520&quot; y=&quot;122&quot; fill=&quot;#35495e&quot; text-anchor=&quot;middle&quot;&gt;current pixel vUv&lt;/text&gt;
    &lt;circle cx=&quot;520&quot; cy=&quot;166&quot; r=&quot;8&quot; fill=&quot;#646cff&quot; /&gt;
    &lt;path d=&quot;M 520 166 C 472 132 386 126 324 150&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;3&quot; marker-end=&quot;url(#arrow-advection-en)&quot; /&gt;
    &lt;text x=&quot;420&quot; y=&quot;194&quot; fill=&quot;#666&quot; text-anchor=&quot;middle&quot;&gt;trace backward by velocity&lt;/text&gt;
    &lt;path d=&quot;M 242 220 C 330 206 438 210 564 230&quot; fill=&quot;none&quot; stroke=&quot;#f59e0b&quot; stroke-width=&quot;4&quot; opacity=&quot;0.7&quot; marker-end=&quot;url(#arrow-advection-en)&quot; /&gt;
    &lt;text x=&quot;380&quot; y=&quot;260&quot; fill=&quot;#666&quot; text-anchor=&quot;middle&quot;&gt;velocity u tries to move pigment down and right&lt;/text&gt;
    &lt;text x=&quot;70&quot; y=&quot;294&quot; fill=&quot;#35495e&quot;&gt;coord = vUv - dt <em> velocity </em> texelSize&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<pre><code class="language-glsl">vec2 vel = texture(uVelocity, vUv).xy;
vec2 coord = vUv - dt * vel * uTexelSize;
outColor = dissipation * texture(uSource, coord);
</code></pre>
<p>The same shader moves both <code>velocity</code> and <code>wet</code>.</p>
<pre><code class="language-ts">this.run(&quot;advect&quot;, this.velocity.write, {
  dissipation: 0.986,
  dt,
  uSource: this.velocity.readTexture,
  uVelocity: this.velocity.readTexture,
});

this.run(&quot;advect&quot;, this.wet.write, {
  dissipation: 0.998,
  dt,
  uSource: this.wet.readTexture,
  uVelocity: this.velocity.readTexture,
});
</code></pre>
<p>Velocity dissipates at <code>0.986</code>, while wet pigment dissipates at <code>0.998</code>.
Motion fades sooner than pigment.
Semi-Lagrangian advection is stable, but it blurs.
For background bleeding, that blur is usually useful.</p>
<h2>Add Back A Little Curl</h2>
<p>Semi-Lagrangian advection tends to lose small rotations.
The solver first computes curl, then uses vorticity confinement to reintroduce a little swirl.</p>
<pre><code class="language-glsl">outColor = vec4(
  0.5 * (right.y - left.y - (top.x - bottom.x)),
  0.0,
  0.0,
  1.0
);
</code></pre>
<p>In 2D, curl can be stored as a scalar.
The next pass looks at the gradient of <code>abs(curl)</code> and pushes around the curl center.</p>
<pre><code class="language-glsl">vec2 force = 0.5 * vec2(abs(top.x) - abs(bottom.x), abs(right.x) - abs(left.x));
force /= length(force) + 0.0001;
force *= strength * center * vec2(1.0, -1.0);
vec2 vel = texture(uVelocity, vUv).xy;
outColor = vec4(vel + force * dt, 0.0, 1.0);
</code></pre>
<p><code>strength</code> is <code>18</code>.
Higher values make the motion showier, but also louder as a background.
The goal is not visible water turbulence; it is slight uneven pigment escape.</p>
<h2>Boundaries Depend On The Field</h2>
<p>After several passes, the solver applies a boundary pass.
Near an edge, it samples the neighboring texel and multiplies by a scale.</p>
<pre><code class="language-glsl">if (uv.x &lt; uTexelSize.x) data = scale * texture(uTarget, uv + vec2(uTexelSize.x, 0.0));
if (uv.x &gt; 1.0 - uTexelSize.x) data = scale * texture(uTarget, uv - vec2(uTexelSize.x, 0.0));
if (uv.y &lt; uTexelSize.y) data = scale * texture(uTarget, uv + vec2(0.0, uTexelSize.y));
if (uv.y &gt; 1.0 - uTexelSize.y) data = scale * texture(uTarget, uv - vec2(0.0, uTexelSize.y));
</code></pre>
<p>For <code>velocity</code>, <code>scale = -1</code>, so velocity reflects at the wall.
For <code>wet</code>, <code>scale = 0</code>, so wet pigment that reaches the outside disappears.
For <code>pressure</code>, <code>scale = 1</code>, so pressure continues across the edge.
One boundary shader is reused, but the sign depends on the meaning of the field.</p>
<h2>Pressure Projection Reduces Divergence</h2>
<p>Splatting and vorticity can make velocity expand or collapse.
The solver first computes divergence.</p>
<pre><code class="language-glsl">outColor = vec4(
  0.5 * (right.x - left.x + top.y - bottom.y),
  0.0,
  0.0,
  1.0
);
</code></pre>
<p>Divergence marks places where flow is appearing or disappearing.
To reduce it, pressure is solved with Jacobi iterations.</p>
<p>&lt;div&gt;
&lt;svg viewBox=&quot;0 0 760 320&quot; role=&quot;img&quot; aria-labelledby=&quot;projection-title-en&quot;&gt;
  &lt;title id=&quot;projection-title-en&quot;&gt;What pressure projection does&lt;/title&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;arrow-projection-en&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;8.5&quot; refY=&quot;5&quot; markerWidth=&quot;5&quot; markerHeight=&quot;5&quot; orient=&quot;auto-start-reverse&quot;&gt;
      &lt;path d=&quot;M 0 0 L 10 5 L 0 10 z&quot; fill=&quot;#35495e&quot; /&gt;
    &lt;/marker&gt;
    &lt;linearGradient id=&quot;code-wash-projection-en&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0.045&quot; /&gt;
      &lt;stop offset=&quot;72%&quot; stop-color=&quot;#5a8abf&quot; stop-opacity=&quot;0&quot; /&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;0&quot; y=&quot;0&quot; width=&quot;760&quot; height=&quot;320&quot; fill=&quot;url(#code-wash-projection-en)&quot; /&gt;
  &lt;path d=&quot;M 0 0.5 H 760 M 0 319.5 H 760&quot; stroke=&quot;#2f3b47&quot; stroke-opacity=&quot;0.16&quot; /&gt;
  &lt;text x=&quot;48&quot; y=&quot;62&quot; font-size=&quot;18&quot; font-weight=&quot;700&quot; fill=&quot;#35495e&quot;&gt;Pressure Projection Smooths Sources&lt;/text&gt;
  &lt;g font-family=&quot;system-ui, sans-serif&quot; font-size=&quot;14&quot; text-anchor=&quot;middle&quot;&gt;
    &lt;ellipse cx=&quot;174&quot; cy=&quot;171&quot; rx=&quot;122&quot; ry=&quot;84&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;174&quot; y=&quot;112&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;before projection&lt;/text&gt;
    &lt;text x=&quot;174&quot; y=&quot;136&quot; fill=&quot;#9f6464&quot;&gt;large divergence&lt;/text&gt;
    &lt;circle cx=&quot;174&quot; cy=&quot;194&quot; r=&quot;16&quot; fill=&quot;#ff6b6b&quot; opacity=&quot;0.35&quot; /&gt;
    &lt;path d=&quot;M 174 194 L 174 160&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 174 194 L 226 194&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 174 194 L 174 238&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 174 194 L 122 194&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;line x1=&quot;302&quot; y1=&quot;171&quot; x2=&quot;458&quot; y2=&quot;171&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;text x=&quot;380&quot; y=&quot;154&quot; fill=&quot;#35495e&quot;&gt;solve pressure&lt;/text&gt;
    &lt;text x=&quot;380&quot; y=&quot;194&quot; fill=&quot;#666&quot;&gt;subtract ∇p&lt;/text&gt;
    &lt;ellipse cx=&quot;586&quot; cy=&quot;171&quot; rx=&quot;122&quot; ry=&quot;84&quot; fill=&quot;#42b883&quot; opacity=&quot;0.1&quot; /&gt;
    &lt;text x=&quot;586&quot; y=&quot;112&quot; fill=&quot;#35495e&quot; font-weight=&quot;700&quot;&gt;after projection&lt;/text&gt;
    &lt;text x=&quot;586&quot; y=&quot;136&quot; fill=&quot;#52745f&quot;&gt;divergence reduced&lt;/text&gt;
    &lt;path d=&quot;M 546 164 C 568 146 604 146 626 164&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 626 184 C 604 202 568 202 546 184&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 546 164 C 532 178 532 190 546 204&quot; fill=&quot;none&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;path d=&quot;M 626 184 C 640 170 640 158 626 144&quot; fill=&quot;none&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrow-projection-en)&quot; /&gt;
    &lt;text x=&quot;586&quot; y=&quot;238&quot; fill=&quot;#666&quot;&gt;less explosive flow&lt;/text&gt;
  &lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<pre><code class="language-glsl">float div = texture(uDivergence, vUv).x;
outColor = vec4(
  (left.x + right.x + bottom.x + top.x - div) * 0.25,
  0.0,
  0.0,
  1.0
);
</code></pre>
<p>Then the pressure gradient is subtracted from velocity.</p>
<pre><code class="language-glsl">vec2 vel = texture(uVelocity, vUv).xy;
outColor = vec4(
  vel - 0.5 * vec2(right.x - left.x, top.x - bottom.x),
  0.0,
  1.0
);
</code></pre>
<p>This does not make the field perfectly incompressible.
The pressure solve only runs for <code>8</code> iterations.
For a watercolor background, strict volume conservation matters less than keeping the stroke edge from looking like it explodes.</p>
<h2>Split Wet And Dry</h2>
<p>The WebGL2 solver mainly owns <code>velocity</code>, <code>wet</code>, <code>dry</code>, and <code>pressure</code>.</p>
<pre><code class="language-text">velocity  water motion
wet       pigment that can still move
dry       pigment fixed into the paper
pressure  field used to keep velocity coherent
</code></pre>
<p>Incoming color is not written directly into <code>dry</code>.
It is splatted into <code>wet</code>.
The stroke first turns pointer delta into direction, radius, force, and opacity.</p>
<pre><code class="language-ts">const speed = Math.hypot(stroke.deltaX, stroke.deltaY);
const directionX = speed &gt; 0.00001 ? stroke.deltaX / speed : 1;
const directionY = speed &gt; 0.00001 ? stroke.deltaY / speed : 0;
const radius = 0.00018 + stroke.pressure * 0.00034;
const force = Math.min(46, 8 + speed * 2200);
const opacity = Math.min(0.42, 0.18 + stroke.alpha * 3.8 + stroke.pressure * 0.045);
</code></pre>
<p>One stroke updates both velocity and wet pigment.
The velocity splat carries motion; the wet splat carries pigment absorption.</p>
<pre><code class="language-ts">this.splatBuffer(
  this.velocity,
  stroke.x,
  stroke.y,
  directionX * force,
  directionY * force,
  0,
  1,
  radius * 1.05,
  0,
  directionX,
  directionY,
);

this.splatBuffer(
  this.wet,
  stroke.x,
  stroke.y,
  (1 - stroke.red) * opacity,
  (1 - stroke.green) * opacity,
  (1 - stroke.blue) * opacity,
  opacity,
  radius,
  speed &gt; 0.0009 ? 1 : 0,
  directionX,
  directionY,
);
</code></pre>
<p>The solver stores <code>1 - rgb</code>, treating pigment as light absorption rather than paint added on top of paper.
It also adds six small outward velocity splats around the center.
This is not a physically exact capillary model, but it gives the stroke edge somewhere to bleed.</p>
<pre><code class="language-ts">for (let index = 0; index &lt; 6; index += 1) {
  const angle = (index / 6) * Math.PI * 2;
  const offset = Math.sqrt(radius) * 0.42;
  const bloomX = clamp01(stroke.x + Math.cos(angle) * offset);
  const bloomY = clamp01(stroke.y + Math.sin(angle) * offset);
  this.splatBuffer(
    this.velocity,
    bloomX,
    bloomY,
    Math.cos(angle) * 28,
    Math.sin(angle) * 28,
    0,
    1,
    radius * 0.62,
    0,
    Math.cos(angle),
    Math.sin(angle),
  );
}
</code></pre>
<p>Each frame advects <code>velocity</code> and <code>wet</code>.
It does not advect <code>dry</code>.</p>
<pre><code class="language-glsl">vec2 vel = texture(uVelocity, vUv).xy;
vec2 coord = vUv - dt * vel * uTexelSize;
outColor = dissipation * texture(uSource, coord);
</code></pre>
<p>This separation does most of the work.
Wet pigment moves.
Dry pigment does not.
That turns &quot;a transparent image spreading on screen&quot; into something closer to pigment settling into paper.</p>
<p>The velocity field still needs projection.
The solver computes divergence, iterates pressure, and subtracts the pressure gradient.</p>
<pre><code class="language-ts">this.run(&quot;divergence&quot;, this.divergence, {
  uVelocity: this.velocity.readTexture,
});
this.clearTarget(this.pressure.read);

for (let index = 0; index &lt; pressureIterations; index += 1) {
  this.run(&quot;pressure&quot;, this.pressure.write, {
    uDivergence: this.divergenceTexture,
    uPressure: this.pressure.readTexture,
  });
  this.swap(this.pressure);
  this.applyBoundary(this.pressure, 1);
}
</code></pre>
<p>The pressure solve stops at <code>8</code> iterations.
That is rough as fluid simulation, but good enough for bleeding pigment.
A slightly loose field often reads as paper irregularity instead of error.</p>
<p>Drying is split between <code>dryAccum</code> and <code>dryWet</code>.</p>
<pre><code class="language-glsl">float density = max(max(w.r, w.g), w.b);
float thicknessFactor = 1.0 / (1.0 + density * thicknessK);
float wet1 = max(wet0 - dryRate * thicknessFactor * dt, 0.0);
</code></pre>
<p>Dense pigment dries more slowly.
Thin water disappears earlier, while pooled pigment remains as edges and unevenness.
It is not exact physics, but it is the right perceptual shortcut.</p>
<p>The dry accumulation pass also mixes addition and replacement.</p>
<pre><code class="language-glsl">vec3 transfer = w.rgb * frac * 1.015;
float coverage = 1.0 - exp(-density * 1.8);
vec3 headroom = max(vec3(dryCap) - d.rgb, vec3(0.0));
vec3 added = d.rgb + min(transfer, headroom);
vec3 replaced = mix(d.rgb, w.rgb, frac);
outColor = vec4(mix(added, replaced, coverage), min(d.a + dried, 4.0));
</code></pre>
<p>Pure addition makes repeated strokes sink toward mud.
Pure replacement erases transparent glazing.
Thin pigment stays closer to additive layering; dense pigment moves closer to replacement.
That keeps both pale washes and heavier pools.</p>
<h2>Return To A Transparent Canvas</h2>
<p>Inside the solver, wet and dry pigment are first composited against an assumed paper color.</p>
<pre><code class="language-glsl">vec3 paper = vec3(0.96, 0.945, 0.92);
vec3 afterDry = paper * exp(-dry.rgb * 2.2);
vec3 wetColor = paper * exp(-wet.rgb * 2.2);
vec3 glazed = afterDry * exp(-wet.rgb * 2.2);
</code></pre>
<p>The actual page already has a separate WebGPU paper layer, so the final brush output must stay transparent.</p>
<pre><code class="language-glsl">float density = max(wetDensity, dryDensity);
float edge = smoothstep(0.002, 0.018, length(vec2(dFdx(density), dFdy(density))));
float alpha = clamp(density * 1.15 + wetAmt * 0.18 + edge * 0.12, 0.0, 0.62);
outColor = vec4(color, alpha);
</code></pre>
<p>The offscreen WebGL canvas is copied back into the visible 2D canvas.</p>
<pre><code class="language-ts">context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(this.canvas, 0, 0, canvas.width, canvas.height);
</code></pre>
<p>CSS then sinks it into the paper.</p>
<pre><code class="language-css">.brush-canvas {
  mix-blend-mode: multiply;
}
</code></pre>
<p>The paper and brush are not combined into one renderer because their failure scopes are different.
Paper can survive without brush.
Brush can fall back to Canvas 2D.
Mobile can skip both dynamic paths.
Separate layers make those choices simple.</p>
<h2>The Canvas 2D Fallback Keeps The Same Contract</h2>
<p>When WebGL2 is unavailable, <code>createBrushLayer</code> is used.
It is not a fluid solver, but it keeps the wet/dry idea.</p>
<pre><code class="language-text">dryLayer
wetLayer
wetBlooms
wetDryFrames
</code></pre>
<p>Strokes are drawn into <code>wetLayer</code>, then slowly moved into <code>dryLayer</code>.</p>
<pre><code class="language-ts">dryContext.globalCompositeOperation = &quot;multiply&quot;;
dryContext.globalAlpha = 0.026;
dryContext.drawImage(wetLayer, 0, 0);

wetContext.globalCompositeOperation = &quot;destination-out&quot;;
wetContext.fillStyle = &quot;rgba(0, 0, 0, 0.018)&quot;;
wetContext.fillRect(0, 0, wetLayer.width, wetLayer.height);
</code></pre>
<p>The WebGL2 solver is not the essence of the effect.
The essence is that pigment can be wet, can dry, and those two states have a time gap.
The fallback preserves that contract.</p>
<h2>Do Not Change Color Too Fast</h2>
<p>Brush color changes every 36 draws.</p>
<pre><code class="language-ts">const brushColorHoldDraws = 36;
</code></pre>
<p>Changing color on every pointermove creates a rainbow marker, not watercolor.
One pigment needs time to dilute and spread.
The palette also stays low-alpha, around <code>0.04</code> to <code>0.05</code>.</p>
<p>For a reading background, layered density is better than loud hue changes.</p>
<h2>Keep Renderer State Out Of Vue</h2>
<p>The Vue state is intentionally small.</p>
<pre><code class="language-ts">const fallbackLayerElement = ref&lt;HTMLElement&gt;();
const fallbackCanvasElement = ref&lt;HTMLCanvasElement&gt;();
const paperCanvasElement = ref&lt;HTMLCanvasElement&gt;();
const brushCanvasElement = ref&lt;HTMLCanvasElement&gt;();
const isDrawingEnabled = ref(false);
</code></pre>
<p>WebGPU devices, WebGL contexts, textures, framebuffers, wet/dry buffers, and previous pointer positions are not reactive state.
They are renderer state.</p>
<p>From Vue&#39;s side, the brush layer is just an imperative object with <code>draw</code>, <code>resize</code>, and <code>clear</code>.</p>
<pre><code class="language-ts">const brushLayer = createFluidBrushLayer({
  canvas: () =&gt; brushCanvasElement.value,
  isDrawingEnabled: () =&gt; isDrawingEnabled.value,
  maxDpr,
});
</code></pre>
<p>When graphics live inside a UI framework, it is tempting to make everything reactive.
But state that never updates the template only adds tracking cost and confusion.
The renderer should own renderer state.</p>
<h2>Cleanup Matters</h2>
<p>The background attaches window-level listeners.
That makes cleanup part of the feature.</p>
<pre><code class="language-ts">onUnmounted(() =&gt; {
  if (resizeFrame !== 0) {
    cancelAnimationFrame(resizeFrame);
  }
  removeViewportListeners?.();
  removeBrushListeners?.();
  paperCleanup?.();
});
</code></pre>
<p>The WebGPU cleanup calls <code>unconfigure()</code> too.</p>
<pre><code class="language-ts">return () =&gt; {
  disposed = true;
  if (paperResizeFrame !== 0) {
    cancelAnimationFrame(paperResizeFrame);
  }
  removeViewportListeners();
  gpuContext.unconfigure();
};
</code></pre>
<p>A background feels minor, but it touches the whole page.
Stopping it cleanly is part of implementing it.</p>
<h2>Test Properties, Not Beauty</h2>
<p>It is hard to automatically test whether watercolor looks beautiful.
It is much easier to test the properties that should not regress.</p>
<p>The Playwright checks look for things like these.</p>
<ul>
<li><p>the fallback canvas is not blank</p>
</li>
<li><p><code>.watercolor-bg-container</code> does not cover content</p>
</li>
<li><p>the paper canvas initializes when WebGPU is available</p>
</li>
<li><p>mobile uses <code>bg-sp.png</code> and hides the canvases</p>
</li>
<li><p>the tooltip does not stay visible after hover</p>
</li>
<li><p>brush color does not change too quickly within one stroke</p>
</li>
<li><p>the bleeding edge expands or darkens after a delay</p>
</li>
<li><p>pointer-event bursts finish within a bounded time</p>
</li>
</ul>
<p>The tests are not pixel-perfect.
They protect the behavioral and perceptual contracts.
For a background, that is more useful than locking down an exact image.</p>
<h2>Summary</h2>
<p>The tech behind transparent watercolor on the Web is not just about solving fluid accurately.
On this site, these choices matter more.</p>
<ul>
<li><p>split paper and brush into separate layers</p>
</li>
<li><p>draw Canvas 2D fallback paper before WebGPU starts</p>
</li>
<li><p>bake paper once with WebGPU and keep it still</p>
</li>
<li><p>cap DPR so the background does not pay for unnecessary fragments</p>
</li>
<li><p>enable brush input only for fine-pointer desktop contexts</p>
</li>
<li><p>lazily initialize the WebGL2 solver and fall back to Canvas 2D</p>
</li>
<li><p>split pigment into <code>wet</code> and <code>dry</code>, and never advect <code>dry</code></p>
</li>
<li><p>keep color stable long enough to feel like one pigment</p>
</li>
<li><p>keep renderer state out of Vue reactivity</p>
</li>
<li><p>use a static background on mobile</p>
</li>
<li><p>test properties instead of pixels</p>
</li>
</ul>
<p>Transparent watercolor does not require solving water, paper, and pigment perfectly.
It only needs to make the reader feel paper, wetness, and drying over a short time.
And when the environment cannot support that, it should do nothing.
For a background, quiet failure is part of the implementation.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Keep the Discussion as a Graph</title>
      <link>https://wtrclred.vercel.app/posts/15</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/15</guid>
      <description>Information is essentially graph-shaped, and folding it into a tree is wrong.</description>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T12:02:04.000Z</dc:date>
      <category>philosophy</category>
      <content:encoded><![CDATA[<h2>Introduction</h2>
<p>When you discuss something, what kind of structure do you imagine underneath the discussion?</p>
<p>I suspect many people naturally assume a <strong>tree with a limited scope</strong>.
There is a root topic, branches beneath it, and the discussion proceeds inside one branch at a time.
When the conversation drifts, someone says &quot;let&#39;s come back to the topic.&quot;
When another branch appears, someone cuts it off as &quot;a separate issue.&quot;</p>
<p>This attitude is generally preferred in practice.
It appears to keep the discussion under control, and it appears to make meetings easier to time-box.
People often say that, when explaining something, it is kinder to reduce it to a tree.</p>
<p>But I do not like that premise at all.
More strongly, I think <strong>it is wrong to proceed with a discussion as a tree</strong>.</p>
<p>The essential structure of information is a graph, and a tree is only a partial structure inside it.
To proceed with a discussion as a tree is often to drop edges that were really there, then treat that missing structure as the official one.</p>
<p>And those dropped edges come back later under the name &quot;insufficient consideration.&quot;</p>
<h2>Trees Are Wrong</h2>
<p>Borrowing the language of graph theory, a tree can roughly be understood as a connected graph with no cycles.
In other words, a tree is a special kind of graph.
The graph comes first; the tree is what you get after imposing strong constraints on it.</p>
<p>I think the same relationship holds for information.
It is not that there is information that naturally belongs in a tree.
It is that information simple enough to fit in a tree can sometimes be treated as a tree.
And discussions are almost never that simple.</p>
<p>One piece of information does not necessarily have only one parent.
One issue does not necessarily belong under only one root.
One piece of evidence does not necessarily complete itself inside a single branch.</p>
<p>Even the file system shows this problem immediately.
Should a file live under &quot;work,&quot; under &quot;personal research,&quot; under &quot;2026,&quot; or under a specific project?
The answer is usually just a convenience of the moment.
Once you pick a single parent, you lose the naturalness of the other contexts.
That is why we use search, tags, links, shortcuts, and things like symbolic links as escape hatches.</p>
<p>This is not merely a UI preference.
Forcing information into a tree places a strong, and often wrong, assumption on the shape of that information.</p>
<p>This problem is old.
In <a href="https://www.w3.org/History/1945/vbush/" target="_blank" rel="noopener noreferrer">As We May Think</a>, published in 1945, Vannevar Bush imagined a future for mechanically handling records and described ideas like associative trails through related records.
In today&#39;s language, that imagination is deeply hypertextual and graph-like.</p>
<p>You can see the same direction in modern tools.
<a href="https://obsidian.md/help/plugins/graph" target="_blank" rel="noopener noreferrer">Obsidian&#39;s Graph view</a> visualizes notes as nodes and internal links as edges.
<a href="https://prtimes.jp/main/html/rd/p/000000323.000027275.html" target="_blank" rel="noopener noreferrer">Scrapbox was renamed Helpfeel Cosense in 2024</a>, but its spirit of growing knowledge through loosely linked pages is much closer to a graph than to a tree.</p>
<p>What these examples show is the obvious fact that information cannot be handled by parent-child relationships alone.
I want to be just as strict about this in discussion.</p>
<h2>Why People Prefer Trees</h2>
<p>So why do people prefer tree-shaped discussions so much?</p>
<p>The reason is simple: human working memory is limited.</p>
<p>George A. Miller&#39;s famous paper <a href="https://psychclassics.yorku.ca/Miller/" target="_blank" rel="noopener noreferrer">The Magical Number Seven, Plus or Minus Two</a> is a classic reference point for the limits of human information processing capacity.
But the number &quot;seven&quot; should not be worshiped too casually.
In <a href="https://www.cambridge.org/core/services/aop-cambridge-core/content/view/44023F1147D4A1D44BDC0AD226838496/S0140525X01003922a.pdf/magical_number_4_in_shortterm_memory_a_reconsideration_of_mental_storage_capacity.pdf" target="_blank" rel="noopener noreferrer">The magical number 4 in short-term memory</a>, Nelson Cowan reframes short-term memory capacity as a smaller problem, roughly three to five chunks.</p>
<p><a href="https://link.springer.com/article/10.1007/s10648-010-9145-4" target="_blank" rel="noopener noreferrer">Cognitive Load Theory</a> also describes how, in complex tasks, too many interacting information elements can overwhelm capacity-limited working memory.</p>
<p>So it is natural for people to feel, &quot;please make this simpler,&quot; &quot;please narrow the issue,&quot; or &quot;let&#39;s only talk about this for now.&quot;
That is not evil.
In many cases, it is an important form of care toward the listener.</p>
<p>But there is one thing we must not confuse.</p>
<p><strong>The fact that humans can handle only a small amount of information at once</strong> and <strong>the claim that the problem itself can be represented as a small tree</strong> are completely different.</p>
<p>The former is a limitation of the receiver.
The latter is a claim about the structure of the object.
Mixing them up is dangerous.</p>
<p>Trees are preferred not because they are correct.
They feel comfortable only because the human processing system is narrow.</p>
<p>Comfort and correctness are different things.</p>
<h2>Wrong Tree-Shaking</h2>
<p>When working with people, evaluations like &quot;smart person&quot; or &quot;clear communicator&quot; are often given to people who can reduce and simplify information to the necessary and sufficient amount for the moment.</p>
<p>I understand that evaluation.
Someone who can adjust the order of explanation to match the listener is genuinely strong.</p>
<p>But <strong>adjusting the order of speech</strong> and <strong>transforming the structure of the discussion into a tree</strong> are different things.
The former is choosing a traversal over a graph.
The latter is deleting edges from the graph.</p>
<p>Very few people can actually do the latter correctly.</p>
<p>To decide what can be dropped, you already need to see the whole graph.
Whether an edge is unnecessary for the current explanation, or whether it is an important dependency that will later overturn the decision, cannot be judged by looking only at a tree-shaped slice.</p>
<p>In many cases, people are not performing necessary and sufficient simplification.
They are simply dropping the edges they cannot process.
This is wrong tree-shaking.</p>
<p>They think they are removing unused code, but they are actually deleting an import with side effects.
The same thing happens in discussion.</p>
<ul>
<li><p>Treating something as &quot;not the current topic&quot; when it was actually a precondition</p>
</li>
<li><p>Treating something as &quot;a detail&quot; when it actually determines feasibility</p>
</li>
<li><p>Treating something as &quot;another team&#39;s problem&quot; when it actually defines the responsibility boundary</p>
</li>
<li><p>Treating something as &quot;emotional&quot; when it is actually a signal that consensus is about to fail</p>
</li>
</ul>
<p>Then, later, things mysteriously do not work.
Understanding mysteriously diverges.
The specification mysteriously flips.</p>
<p>That happens because an edge that mattered was dropped during the discussion.</p>
<h2>Focus Is Also a Graph Operation</h2>
<p>Of course, I am not saying that you must say all information out loud at once.
Humans do not have that much working memory, and speech and writing both have order.</p>
<p>The important thing is not to treat focus as <strong>deletion</strong>.</p>
<p>Focus is a viewport over a graph.
You choose the node to center, choose the depth, and look at that range.
But you do not pretend that everything outside the viewport does not exist.
This is not a tree.
You are still looking at the graph as a graph; you are only changing the center and radius of the view.</p>
<p>When someone says &quot;let&#39;s only discuss this for now,&quot; what they should mean is:</p>
<blockquote>
<p>We are looking at this node with depth 1 for now.</p>
</blockquote>
<p>When someone says &quot;that is a separate issue,&quot; what they should mean is:</p>
<blockquote>
<p>This is a different node, but the edge remains.</p>
</blockquote>
<p>When someone says &quot;we will discuss that later,&quot; what they should mean is:</p>
<blockquote>
<p>This edge is not closed, so we will come back to it.</p>
</blockquote>
<p>But in actual discussions, the word &quot;focus&quot; is often used to mean &quot;let&#39;s pretend this does not exist.&quot;
That is bad.</p>
<p>There are times when we only look at part of the graph.
But the object we are looking at remains a graph until the end.
Do not transform the graph into a tree.</p>
<h2>Discussion Is a Dependency Graph</h2>
<p>The graph I am talking about here is not an undirected graph where everything is scattered equally.
Discussions have fairly strong directionality.</p>
<ul>
<li><p>To claim A, B is required.</p>
</li>
<li><p>If C is true, D becomes weaker.</p>
</li>
<li><p>E is a concrete example of F.</p>
</li>
<li><p>G depends on the definition of H.</p>
</li>
<li><p>I constrains the feasibility of J.</p>
</li>
<li><p>K may break consensus around L.</p>
</li>
</ul>
<p>These edges exist.
In other words, discussion is a dependency graph more than a parent-child relationship.</p>
<p>When you force this dependency graph into a tree, you are forced to make an unnatural choice about what the parent should be.
And that parent is usually decided by the meeting organizer, the document heading, the ticket size, the org chart, or the power dynamics of the moment.</p>
<p>But information dependencies do not obey such things.</p>
<p>So I am not saying graphs have no direction.
I am saying that treating them as one-way parent-child relationships is wrong.
Discussion should proceed as a dependency graph.
You can be conscious of which edge you are following right now, but you must not say &quot;there is nothing outside this branch.&quot;</p>
<h2>Externalize It</h2>
<p>To proceed with discussion as a graph, you do not need to rely only on the human brain.
In fact, trying to process everything only inside the brain is exactly what makes people want to fold things into trees.</p>
<p>If the bottleneck is human working memory, the thing to solve is not &quot;make the problem look smaller.&quot;
It is <strong>externalize the graph</strong>.</p>
<p>In that sense, attempts to treat discussion as a map are important.
For example, <a href="https://cognexus.org/dmforwp2.pdf" target="_blank" rel="noopener noreferrer">Dialog Mapping</a> is described as a way to record the questions, ideas, and pro/con arguments that arise in a conversation as a shared network-like map.
The IBIS background behind it is also close to the idea of treating discussion not as a mere bullet list, but as relationships among questions, proposals, and reasons.</p>
<p>Of course, you do not need a dedicated tool every time.
What matters is that the graph visible during the discussion is preserved somewhere.</p>
<p>For example:</p>
<ul>
<li><p>Name issues as nodes</p>
</li>
<li><p>Give edges types</p>
</li>
<li><p>Treat &quot;we will come back to this later&quot; not as a verbal promise, but as an unresolved edge</p>
</li>
<li><p>Treat &quot;separate issue&quot; not as deletion, but as a link</p>
</li>
<li><p>At the end of the discussion, check whether any edges were dropped</p>
</li>
</ul>
<p>Even that much changes a lot.</p>
<p>We do not need to fear complexity itself so much.
If something complex looks complex, that may be the correct display.
What we should fear is making a complex thing look simple and deleting important dependencies in the process.</p>
<h2>Do Not Confuse Reading Order with Structure</h2>
<p>At least as the internal representation of a discussion, I want to say: throw away the tree.</p>
<p>When writing an article, giving a presentation, or onboarding someone, order becomes necessary.
Headings become necessary.
You need to decide where to start reading and where to go next.</p>
<p>But that is not a tree.
That is a traversal order over a graph.</p>
<p>A genuinely good explainer keeps the graph as a graph, then chooses the path to follow at that moment.
That is why, when the listener asks a different question, they can move to another path.
When the premise changes, they can change the traversal.
When an edge was temporarily outside the view, they can bring it back as soon as it becomes necessary.</p>
<p>A bad explanation, by contrast, mistakes the path for the structure.
It drops edges that were not followed, forgets that it dropped them, and eventually starts believing that the order of explanation is the world itself.</p>
<p>That is dangerous.</p>
<p>What we need is the ability to keep the graph as a graph while choosing how to walk through it.
And if that ability is not strong enough yet, we should preserve the graph itself before simplifying the walk.</p>
<h2>Conclusion</h2>
<p>Proceed with the discussion as a graph.</p>
<p>Of course, human working memory is limited.
So viewports are necessary, and order is necessary.</p>
<p>But those are graph operations.
They are not transformations into a tree.</p>
<p>The essential structure of information is a graph.
A tree is only a partial structure inside it.
Every time you fold a discussion into a tree, edges drop.
When edges drop, dependencies drop.
When dependencies drop, judgment fails.</p>
<p>If the bottleneck that prevents us from handling discussion as a graph is on the human side, I would rather work on extending the human side than carve the problem into pieces.</p>
<p>Externalize it.
Link it.
Preserve unresolved edges.
Even when looking locally, do not stop treating it as a graph.</p>
<p>Keep the discussion as a graph.
That does not mean &quot;talk about everything at once.&quot;
It means: <strong>do not pretend that edges you cannot currently see do not exist</strong>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Folding Hands Before the Prompt: AI, Software, and a Return to Prayer</title>
      <link>https://wtrclred.vercel.app/posts/14</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/14</guid>
      <description>Software engineering loved reproducibility. Probabilistic AI may pull it, just a little, back toward prayer.</description>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T14:50:20.000Z</dc:date>
      <category>philosophy</category>
      <content:encoded><![CDATA[<blockquote>
<p>Note:
This post is half a joke.
I am not suggesting we replace tests with ritual or swap CI for dancing.
I am also not trying to mock any specific religion or faith.
I am borrowing the old language of prayer as a metaphor for uncertainty in the age of AI.</p>
</blockquote>
<h2>Introduction</h2>
<p>Software engineers have spent a very long time loving <strong>reproducibility</strong>.</p>
<ul>
<li><p>same input, same output</p>
</li>
<li><p>same code, same behavior</p>
</li>
<li><p>same test, same result</p>
</li>
<li><p>reproduce the bug, isolate the cause, fix it</p>
</li>
</ul>
<p>That instinct is deeply compatible with the ethos of modern science.
The <a href="https://royalsociety.org/about-us/who-we-are/history/" target="_blank" rel="noopener noreferrer">Royal Society</a> adopted the motto <em>Nullius in verba</em> in 1662: do not simply trust someone&#39;s word; appeal instead to experiment and evidence.
And in a modern formulation, the <a href="https://www.nationalacademies.org/read/25303/chapter/3" target="_blank" rel="noopener noreferrer">National Academies</a> describe reproducibility as obtaining consistent results under the same data, methods, code, and conditions.</p>
<p>That ethos has entered software engineering at the level of muscle memory.
Lint, tests, type checks, CI, deterministic builds.
We spent a long time pulling code away from prayer and toward reproducibility.</p>
<p>And then AI arrived.</p>
<p>AI is useful, fast, and no longer optional.
But at the same time, a large part of what it produces is <strong>probabilistic</strong>.
At least for language models, the natural explanation is that they generate the next token by sampling from a conditional probability distribution over possible continuations.
You can see this both in research descriptions of autoregressive language models and even in practical tooling docs such as <a href="https://help.openai.com/en/articles/5247780-using-logit-bias-to-define-token-probability" target="_blank" rel="noopener noreferrer">OpenAI&#39;s help page on <code>logit_bias</code></a>, which explicitly talks about shifting token likelihood.</p>
<p>So here we are:
members of a profession that loves determinism, now delegating part of our daily work to a <strong>probabilistic transcendent thing</strong>.</p>
<p>That is why I suspect humanity may return, at least a little, to prayer.</p>
<h2>Humanity Has Always Prayed Before What It Could Not Control</h2>
<p>By &quot;prayer,&quot; I do not only mean asking a god for favors.
I mean something broader:
<strong>a human practice of orienting ourselves toward what we cannot fully understand or control</strong>.</p>
<p>The <a href="https://www.britannica.com/topic/prayer" target="_blank" rel="noopener noreferrer">Britannica entry on prayer</a> describes it as communication with the sacred or holy, and notes that prayer appears across religions and historical periods.
It also points out that prayer has long been tied to literature, sacrifice, and collective practice.</p>
<p>Human beings did not merely fold their hands in silence.
We:</p>
<ul>
<li><p>made music for prayer</p>
</li>
<li><p>danced for prayer</p>
</li>
<li><p>built for prayer</p>
</li>
<li><p>wrote books for prayer</p>
</li>
<li><p>organized processions and formal acts for prayer</p>
</li>
</ul>
<p>It does not take much history to see this.
<a href="https://whc.unesco.org/en/list/1572/" target="_blank" rel="noopener noreferrer">Göbekli Tepe</a>, for example, is described by UNESCO as a monumental pre-pottery Neolithic site, often discussed in connection with ritual and communal gatherings.
Humanity, it seems, was building enormous structures for ritual long before it built issue trackers.</p>
<p>A more familiar example, at least from where I stand, is <a href="https://www.britannica.com/topic/Shinto/Types-of-shrines" target="_blank" rel="noopener noreferrer">Britannica&#39;s discussion of Shinto shrines</a>, where ritual life is entangled with music, dance, procession, and offerings.
In Japan, words such as <code>gagaku</code>, <code>kagura</code>, and <code>shishi mai</code> still carry that embodied sense that prayer was never just an inward thought.</p>
<p>That is the key point:
humans have always built <strong>forms</strong> around what exceeded them.
Prayer had gesture, sound, space, architecture, and books.</p>
<h2>Science Cleanly Separated Prayer from Knowledge</h2>
<p>One of the great achievements of modern science was that it separated these things much more sharply.</p>
<ul>
<li><p>it separated desire from verification</p>
</li>
<li><p>it separated communal ritual from factual confirmation</p>
</li>
<li><p>it separated beautiful narrative from reproducible procedure</p>
</li>
</ul>
<p>Of course the real history is messier than that, and the relationship between religion and science cannot be reduced to simple opposition.
But at the level of method, modern knowledge increasingly privileged not &quot;who said it&quot; but &quot;can it be reproduced?&quot;</p>
<p>Software inherited that attitude quite faithfully.
In many ways, software has been one of the most diligent students of modern scientific discipline.</p>
<ul>
<li><p>bugs should be reproducible</p>
</li>
<li><p>tests should remain stable across runs</p>
</li>
<li><p>the same commit should yield the same artifact</p>
</li>
<li><p>rules should live in static analysis rather than in prose</p>
</li>
</ul>
<p>Prayer may remain in the privacy of the human heart, but it was not supposed to enter the decision process of a production deploy.
That was the modern posture.</p>
<h2>But AI Brought Probability Back into the Garden of Determinism</h2>
<p>AI has now brought noise back into that garden.</p>
<p>This is not just the shallow claim that &quot;AI lies.&quot;
It is a more structural issue.</p>
<p>Language models are, at bottom, systems that operate over probability distributions.
A research paper may phrase it in one way, product documentation in another, but the practical consequence is obvious to anyone working with these systems:</p>
<ul>
<li><p>the same request can yield a slightly different result</p>
</li>
<li><p>small prompt changes can suddenly change the model&#39;s mood</p>
</li>
<li><p>context placement matters</p>
</li>
<li><p>some days a model feels brilliant, and the next day it feels mysteriously dull</p>
</li>
</ul>
<p>That puts us in a strange condition.</p>
<p>On one hand, we cannot really refuse AI anymore.
The speed difference is too large.</p>
<p>On the other hand, we cannot treat its output like a perfectly deterministic function.
Of course we can surround it with evaluators, tests, lint, type systems, and review.
But the first draft still arrives with the scent of probability on it.</p>
<p>At that moment, software development becomes, in part, an activity where reproducibility alone is not enough, but where we still have to hope for a good outcome.</p>
<h2>Prayer Returns, Though Not as a Replacement for CI</h2>
<p>Now for the half-joking core claim.</p>
<p>I think software engineers working with AI will gradually invent new rituals.</p>
<p>For example:</p>
<ul>
<li><p>always playing the same music before creating a new repo</p>
</li>
<li><p>reciting the same prompt template before a difficult implementation</p>
</li>
<li><p>treating <code>AGENTS.md</code> almost like scripture</p>
</li>
<li><p>saying &quot;this model is in a good mood today&quot;</p>
</li>
<li><p>wiping the desk before deploy for no technical reason whatsoever</p>
</li>
<li><p>treating inference cost as a kind of offering</p>
</li>
</ul>
<p>Technically, some of these rituals are meaningless.
But not entirely meaningless.
The point of ritual is not only to change the outside world directly.
It is also to <strong>stabilize a community&#39;s posture toward uncertainty</strong>.</p>
<p>Just as prayer in religious history was both personal and communal, AI-age ritual may also align the team&#39;s emotional rhythm.
Nobody truly believes that a small office ceremony will lower perplexity.
But many teams will believe, correctly, that it helps them approach uncertainty together.</p>
<p>That, to me, is a small return of prayer.</p>
<p>Modern software engineering became strong by understanding, decomposing, reproducing, and controlling.
AI introduces a new kind of object:
something we cannot fully understand, yet still must collaborate with.</p>
<p>The answer is not to abandon science.
Quite the opposite.</p>
<ul>
<li><p>lock down what can be made deterministic</p>
</li>
<li><p>keep tests and type checks</p>
</li>
<li><p>strengthen evaluation</p>
</li>
<li><p>widen the boundary of what can be verified mechanically</p>
</li>
</ul>
<p>And then, for the uncertainty that remains, humans may become just a little more ritualistic.</p>
<p>So the workflow of the future engineer may look like this:</p>
<ol>
<li><p>write the tests</p>
</li>
<li><p>tune the seed and the temperature</p>
</li>
<li><p>still feel uneasy, so put on quiet music</p>
</li>
<li><p>finally, fold your hands</p>
</li>
</ol>
<p>That would be a procedure both extremely modern and strangely ancient.</p>
<h2>The Rock of the Greatest Programmer</h2>
<p>And yes, I also imagine a more ridiculous future.</p>
<p>When beginning a new project, we first visit a place that enshrines a <strong>giant rock said to house the spirit of the greatest programmer</strong>.
There we remove our shoes, open our laptops in silence, and recite prompts like liturgy.</p>
<p>&quot;Keep syntax errors far from us.&quot;</p>
<p>&quot;Cast out the mysterious off-by-one.&quot;</p>
<p>&quot;Guide the dependencies of <code>useEffect</code> in righteousness.&quot;</p>
<p>&quot;Grant mercy to this pull request in the eyes of the reviewer.&quot;</p>
<p>Someone rings a small bell.
Someone starts a thin synth pad.
Someone runs <code>pnpm test</code>.</p>
<p>Then everyone bows once toward the rock and says:</p>
<blockquote>
<p>May today&#39;s model be slightly wiser than yesterday&#39;s.</p>
</blockquote>
<p>Do I know whether the spirit of the greatest programmer really lives inside that rock?
Of course not.
But for now, that is not so different from the fact that none of us can fully explain everything going on inside AI systems either.</p>
<h2>Conclusion</h2>
<p>Human beings have always prayed, sung, danced, built, and written in the face of what they could not fully master.
Science took the reproducible parts of that human struggle and sharpened them into method.
Software engineering became one of its best heirs.</p>
<p>And yet AI has reintroduced probability into the center of that world.
That is why I imagine engineers returning, just a little, to prayer.</p>
<p>Not by abandoning tests.
Not by replacing method with superstition.
But by keeping science in one hand and rediscovering, in the other, the posture of prayer.</p>
<p>We are not going back to a pre-scientific age.
We are discovering that even after science, some human gestures remain useful when uncertainty refuses to disappear.</p>
<p>Software development may end up resembling an older human activity than we expected:
hoping for good outcomes, arranging forms, aligning with collaborators, and finally looking upward for just a second.</p>
<p>At the very least, anyone already asking AI to write code has one foot in that future.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Agenty: Designing How Much We Entrust to Coding Agents</title>
      <link>https://wtrclred.vercel.app/posts/13</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/13</guid>
      <description>Agenty is a way to think in percentages about how much we hand over to agents, and where we should keep things static instead.</description>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T12:02:04.000Z</dc:date>
      <category>AI</category>
      <category>agent</category>
      <category>architecture</category>
      <category>static-analysis</category>
      <category>frontend</category>
      <category>mvvm</category>
      <category>typescript</category>
      <content:encoded><![CDATA[<h2>Introduction</h2>
<p>Since Coding Agents became practical, the central design question has shifted a little.
It used to be mostly about how we should write code.
Now it is equally about <strong>how much of that code we should entrust to an agent</strong>.</p>
<p>I want to call that degree of entrustment <strong>Agenty</strong>.</p>
<ul>
<li><p><code>0</code> means we do not entrust anything to the agent</p>
</li>
<li><p><code>100</code> means we entrust everything to the agent</p>
</li>
</ul>
<p>This is not a benchmark for model quality.
It is a <strong>design parameter for repositories and architectures</strong>.</p>
<p>The important point is that higher is not always better.
The goal is not to push an entire repo to <code>100</code>.
The goal is to decide <strong>what should stay near <code>0</code>, and where high Agenty is actually appropriate</strong>.</p>
<p>From that perspective, the <code>*.def.ts</code> / <code>*.impl.ts</code> / <code>*.test.ts</code> split in <a href="https://github.com/ubugeeei/fuckin-strict-nuxt-dmmf" target="_blank" rel="noopener noreferrer">fuckin-strict-nuxt-dmmf</a> is more than a naming rule.
It is a way of embedding into the repo itself which layers should stay low-Agenty and which layers may safely become high-Agenty.</p>
<p>And the core idea is not tied to Nuxt or Vue.
It can be generalized much more broadly.</p>
<h2>Agenty Should Be Designed Locally</h2>
<p>Agenty is not one global knob for an entire repository.
Different layers should carry different values.</p>
<p>I roughly think in three layers:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Typical Agenty</th>
<th>Why</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>def</code> / static rule</td>
<td><code>0 ~ 20</code></td>
<td>Rules, types, boundaries, and state transitions should stay deterministic</td>
</tr>
<tr>
<td><code>spec</code> / declarative test</td>
<td><code>10 ~ 30</code></td>
<td>This still belongs to human responsibility, even if it supplements the definition</td>
</tr>
<tr>
<td><code>impl</code> / wiring</td>
<td><code>60 ~ 100</code></td>
<td>If the constraints are clear, this is where agents can do a lot</td>
</tr>
</tbody>
</table>
<p>The numbers are not mathematically important.
What matters is having a shared language: &quot;<code>def</code> should stay low,&quot; &quot;<code>impl</code> may go high.&quot;</p>
<h2>Where Agenty Should Be Pushed Toward Zero</h2>
<p>The first thing I want to say strongly is this:
<strong>general coding rules and practices should be enforced as deterministically as possible through static analysis</strong>.</p>
<p>This is not where we should seek high Agenty.
This is where Agenty should move toward <code>0</code>.</p>
<p>For example:</p>
<ul>
<li><p>formatting</p>
</li>
<li><p>lint rules</p>
</li>
<li><p>type checking</p>
</li>
<li><p>import boundaries and dependency direction</p>
</li>
<li><p>exhaustiveness checks and unreachable-state detection</p>
</li>
</ul>
<p>These should be things the repo itself can <strong>reject mechanically</strong>.</p>
<p>We should not outsource this to natural-language operations.
Writing guidance in <code>AGENTS.md</code> or in skills can be useful as assistance, but that should not become the law of the repository.</p>
<p>Why not?
Because natural-language operation is fundamentally non-deterministic.</p>
<ul>
<li><p>the model changes</p>
</li>
<li><p>the prompt changes slightly</p>
</li>
<li><p>the context window changes the interpretation</p>
</li>
<li><p>the skill application order changes the result</p>
</li>
</ul>
<p>If you store your invariants there, <strong>the rules themselves become probabilistic</strong>.</p>
<p>That is not &quot;raising Agenty.&quot;
It is simply making your boundaries vague.</p>
<p>My own intuition is that skills should not be the place where a repo&#39;s truth is defined.
They are more like training wheels that help agents follow truths already embedded in the repo.
The truth itself should live in a more static layer.</p>
<h2>Static Analysis Is Also Domain Modeling</h2>
<p>When people hear &quot;static analysis,&quot; they often think only of ESLint or formatting.
But the scope is wider than that.
<strong>Domain modeling also appears at the type level</strong>.</p>
<p>That means the order should be:</p>
<ol>
<li><p>first write definitions in a static layer</p>
</li>
<li><p>then supplement them with declarative tests</p>
</li>
<li><p>and put implementation last</p>
</li>
</ol>
<p>Here, &quot;definition&quot; does not just mean a type alias.
It includes:</p>
<ul>
<li><p>state spaces</p>
</li>
<li><p>events</p>
</li>
<li><p>transitions</p>
</li>
<li><p>input/output contracts</p>
</li>
<li><p>error representations</p>
</li>
<li><p>dependency boundaries</p>
</li>
</ul>
<p>These should be described before the implementation whenever possible.</p>
<p>I care a lot about this order.
If implementation starts first, both humans and agents drift toward &quot;whatever is easiest to write right now.&quot;
If definition comes first, implementation is demoted into the job of satisfying a contract.</p>
<p>That demotion of implementation is exactly what makes high Agenty safe.</p>
<h2>It Looks a Bit Like SDD, But It Is Not the Same</h2>
<p>At first glance, this may look very close to specification-driven development.
And in one sense it is.
The overall direction is similar: put the specification first, establish agreement before implementation.</p>
<p>But I do not see them as identical.
The reason is simple:
<strong>the operational resolution we need is different</strong>.</p>
<p>In a real repository, it is hard to treat vague natural-language specifications as the master source of truth.
Natural language is necessary for RFCs and design discussion, and I absolutely value that.
But natural language inevitably has these properties:</p>
<ul>
<li><p>it leaves room for interpretation</p>
</li>
<li><p>it tends to miss edge conditions</p>
</li>
<li><p>it depends too much on the reader&#39;s experience</p>
</li>
<li><p>it becomes probabilistically unstable when handed to agents</p>
</li>
</ul>
<p>That is why I care less about &quot;starting from natural-language specifications&quot; and more about <strong>forcing those discussions to eventually collapse into <code>def</code> and <code>spec</code></strong>.</p>
<p>In other words:</p>
<ul>
<li><p>RFCs and design discussion can stay in natural language</p>
</li>
<li><p>but the master truth of the repo should be as formal as possible</p>
</li>
</ul>
<p>Do not leave the specification sitting on the altar as prose.
Translate it into types, state transitions, declarative tests, and static constraints.
Only then do humans and agents truly share the same contract.</p>
<p>That point matters a lot to me.
It is not that SDD is bad.
It is that <strong>in the age of Coding Agents, a specification process whose master artifact remains natural language is not enough</strong>.
Using probabilistic and ambiguous prose as the final truth of the repository is hard to sustain in the real world.</p>
<p>So the question is not only &quot;should we write specs first?&quot;
It is also &quot;how far must we formalize them?&quot;</p>
<h2><code>def / spec / impl</code> Is Framework-Independent</h2>
<p>What is interesting in <a href="https://github.com/ubugeeei/fuckin-strict-nuxt-dmmf" target="_blank" rel="noopener noreferrer">fuckin-strict-nuxt-dmmf</a> is not just that it separates <code>*.def.ts</code> from <code>*.impl.ts</code>.
It is that the separation criterion is based on responsibility rather than framework.</p>
<p>The point is not &quot;split things this way because it is Vue.&quot;
The same way of thinking can work for React, Svelte, or server-side applications.</p>
<p>At an abstract level, the split looks like this:</p>
<pre><code class="language-text">feature/
  order.def.ts
  order.spec.ts
  order.impl.ts
</code></pre>
<p>Or more generally:</p>
<pre><code class="language-text">feature/
  order.def.*
  order.spec.*
  order.impl.*
</code></pre>
<p>And the meaning of those files is roughly:</p>
<ul>
<li><p><code>def</code>: low Agenty; changing it implies design intent, so agents should not touch it casually</p>
</li>
<li><p><code>spec</code>: low-to-medium Agenty; this supplements acceptance conditions and declarative scenarios</p>
</li>
<li><p><code>impl</code>: high Agenty; this is the part with the most freedom inside the constraint space</p>
</li>
</ul>
<p>Once that split exists, both instructions and operations become clearer.</p>
<ul>
<li><p>&quot;Only change <code>def</code> when there is an explicit design change&quot;</p>
</li>
<li><p>&quot;Fix <code>impl</code> so that it satisfies <code>spec</code>&quot;</p>
</li>
<li><p>&quot;Read diffs in <code>def</code> as specification changes&quot;</p>
</li>
<li><p>&quot;Read diffs in <code>impl</code> as implementation work&quot;</p>
</li>
</ul>
<p>This is the point where Agenty becomes topology in the repository.</p>
<h2>Agenty Is Also a Framework for Education</h2>
<p>This is not only about repo design or agent operations.
It is also a <strong>framework for educating engineers</strong>.</p>
<p>To be honest, writing <code>def</code> in a necessary-and-sufficient way is difficult.
It is not an easy task.</p>
<ul>
<li><p>What counts as state?</p>
</li>
<li><p>What counts as an event?</p>
</li>
<li><p>Where should we identify things as the same, and where should we split cases?</p>
</li>
<li><p>Which invalid states should be represented explicitly?</p>
</li>
<li><p>Which boundaries should be fixed statically?</p>
</li>
</ul>
<p>Writing these things down formally, without escaping into implementation, takes real training.</p>
<p>But I think that is good.
More than that, I think it <strong>should</strong> be hard.</p>
<p>Because the ability to express domain specification formally before implementation is becoming more important.
As Coding Agents become stronger, the scarcity value of &quot;typing fast&quot; goes down.
What goes up instead is the value of knowing:</p>
<ul>
<li><p>what should be defined at all</p>
</li>
<li><p>what can safely be delegated</p>
</li>
<li><p>which contracts are strong enough for automation</p>
</li>
<li><p>whether generated implementation truly satisfies the specification</p>
</li>
</ul>
<p>In that sense, writing <code>def</code> is not just a preprocessing step.
It trains the actual muscle of <strong>formalizing a domain</strong>.</p>
<p>This is not totally new.
It is close to familiar practices such as &quot;write an RFC before implementation&quot; or &quot;discuss the design before implementation.&quot;
Coding Agents simply make the importance of that order much stronger.</p>
<p>In the past, even if design was sloppy, humans often reconciled the mess while writing the implementation.
But once agents can generate large amounts of implementation quickly, <strong>sloppiness in the definition layer gets amplified downstream</strong>.
That is why discussing <code>def</code> first and tightening <code>spec</code> first now has educational weight as well.</p>
<p>I think the question of how we should train newer engineers in the age of Coding Agents is already becoming a real topic, or at least is clearly moving in that direction.
If agents write much of the implementation, then what exactly should junior engineers learn?</p>
<p>This framework can be one answer.</p>
<ul>
<li><p>write <code>def</code> first</p>
</li>
<li><p>make acceptance conditions explicit in <code>spec</code></p>
</li>
<li><p>build <code>impl</code> together with agents</p>
</li>
<li><p>review the result against the contract and the diff</p>
</li>
</ul>
<p>That shifts the focus of training away from typing speed and memorization, and toward:</p>
<ul>
<li><p>carving up the domain</p>
</li>
<li><p>designing boundaries</p>
</li>
<li><p>describing state transitions</p>
</li>
<li><p>expressing specifications precisely</p>
</li>
<li><p>verifying generated artifacts</p>
</li>
</ul>
<p>I think that is healthy.
Implementation skill does not become unnecessary.
In fact, good <code>def</code> and <code>spec</code> still require implementation intuition.
But the order changes.
The center of gravity moves toward <strong>writing the specification first, then reading, correcting, and evaluating implementation</strong>.</p>
<p>And this is sustainable not only for individuals, but for organizations too.
Knowledge stops living only in a senior engineer&#39;s head or in the exact phrasing of some lucky prompt.
Instead it accumulates in the repository as static assets such as <code>def</code> and <code>spec</code>.
That helps education, maintenance, and handover at the same time.</p>
<h2>Where High Agenty Actually Belongs</h2>
<p>So where should high Agenty go?</p>
<p>My answer is:
<strong>places where multiple implementations are acceptable, and correctness can still be checked mechanically afterward</strong>.</p>
<p>For example:</p>
<ul>
<li><p>mappings</p>
</li>
<li><p>adapters</p>
</li>
<li><p>UI wiring</p>
</li>
<li><p>API client composition</p>
</li>
<li><p>connecting view and state</p>
</li>
<li><p>domain implementation with lots of boilerplate</p>
</li>
</ul>
<p>These are areas where there may be many possible implementations, but the conditions to satisfy can still be written down relatively clearly.
If contract and spec are fixed first, the implementation side can become quite high-Agenty.</p>
<p>By contrast, high Agenty is dangerous in places like:</p>
<ul>
<li><p>the state machine itself</p>
</li>
<li><p>exceptional business rules</p>
</li>
<li><p>boundary-value and invalid-state definitions</p>
</li>
<li><p>architectural boundaries</p>
</li>
<li><p>repo-wide coding rules</p>
</li>
</ul>
<p>Those are not things you can safely &quot;fix later.&quot;
If they drift, every layer of automation after them becomes unstable.</p>
<h2>In Web Frontend, Write the VM into <code>def</code></h2>
<p>This matters especially in <strong>web frontend</strong>.</p>
<p>I think we should revisit an MVVM-like separation.
But my interest is not &quot;let&#39;s make a ViewModel class.&quot;
My interest is <strong>writing the state transitions of the VM layer as definitions before implementation</strong>.</p>
<p>For example:</p>
<pre><code class="language-ts">export type CheckoutState =
  | { kind: &quot;Idle&quot;; cart: Cart }
  | { kind: &quot;Submitting&quot;; cart: Cart }
  | { kind: &quot;Succeeded&quot;; orderId: OrderId }
  | { kind: &quot;Failed&quot;; cart: Cart; reason: CheckoutError };

export type CheckoutEvent =
  | { type: &quot;Submit&quot; }
  | { type: &quot;Resolve&quot;; orderId: OrderId }
  | { type: &quot;Reject&quot;; reason: CheckoutError };

export type Transition = (state: CheckoutState, event: CheckoutEvent) =&gt; CheckoutState;
</code></pre>
<p>Something like this belongs in a file such as <code>checkout.vm.def.ts</code>.</p>
<p>The point is not to freeze how the UI looks.
The point is to freeze <strong>what states the screen has, and how events move it between them</strong>.</p>
<p>That layer should stay low-Agenty.
It directly encodes product behavior.</p>
<p>By contrast, the following may tolerate higher Agenty once the contract is fixed:</p>
<ul>
<li><p>mapping state into props</p>
</li>
<li><p>splitting components</p>
</li>
<li><p>wiring event handlers</p>
</li>
<li><p>implementing side effects</p>
</li>
</ul>
<p>But there is an important caveat here:
<strong>the part corresponding to <code>V</code> in MVVM is still not easy to control well through AI alone</strong>.
Layout, typography, spacing, motion, and visual hierarchy are not automatically solved just because the state contract is good.
That layer has a difficulty of a different kind.</p>
<p>I think the <code>V</code> side should often be treated not as an extension of state management, but as a <strong>separate source of truth integrated with design tools</strong>.
For example:</p>
<ul>
<li><p>screen states and transitions live in <code>VM def</code></p>
</li>
<li><p>acceptance conditions live in <code>VM spec</code></p>
</li>
<li><p>layout and visual structure live in design tools</p>
</li>
<li><p>code exists to connect those worlds</p>
</li>
</ul>
<p>So Agenty in web frontend is not a single axis.
At minimum:</p>
<ul>
<li><p><code>VM</code> can be split into low-Agenty definition and higher-Agenty implementation</p>
</li>
<li><p><code>V</code> is still hard to steer with AI alone, and should be discussed with design systems and design tools as first-class peers</p>
</li>
</ul>
<p>That is why, in web frontend, I would frame it like this:</p>
<ul>
<li><p><code>VM def</code> should stay low-Agenty</p>
</li>
<li><p><code>VM spec</code> should stay medium</p>
</li>
<li><p><code>VM impl</code> may go high</p>
</li>
<li><p><code>View</code> should be handled on a separate axis, connected to design tools rather than collapsed into state management</p>
</li>
</ul>
<p>If you do that, the semantics of product behavior remain anchored in the repository through <code>VM</code>, while the visual layer is delegated to a more appropriate design workflow.
In other words, you do not have to force meaning and form into the same box.</p>
<h2>This Also Improves Operations and Code Review</h2>
<p>This design is not merely AI-friendly.
It matters in day-to-day source code operations as well.</p>
<p>First, it gives you <strong>separation of specification</strong>.
If <code>def</code> and <code>spec</code> are placed in front, the diff separates &quot;what we intended to change&quot; from &quot;how we realized it.&quot;</p>
<p>Second, it improves <strong>editability by agents</strong>.
If the editable region is concentrated in <code>impl</code>, the instruction becomes much simpler:
&quot;implement this while preserving the contract.&quot;</p>
<p>Third, it improves <strong>reviewability</strong>.
Reviewers no longer have to read every diff in the same mode.</p>
<ul>
<li><p>changes in <code>def</code> imply specification review</p>
</li>
<li><p>changes in <code>spec</code> imply expectation review</p>
</li>
<li><p>changes only in <code>impl</code> imply implementation review</p>
</li>
</ul>
<p>This meaning of diffs becomes more important in a high-Agenty world.
Agents can write a huge amount of code.
Human review bandwidth does not expand at the same rate.</p>
<p>That is exactly why the diffs humans truly need to read must remain as static, declarative, and small as possible.
And those static, declarative diffs are also the assets that can be taught, handed over, and maintained sustainably across an organization.</p>
<h2>Conclusion</h2>
<p>What I want to express with the word Agenty is not merely &quot;how much should we let AI do?&quot;
It is a design question about <strong>which parts may remain probabilistic, and which parts must stay decisively fixed</strong>.</p>
<p>General rules, domain boundaries, state transitions, contracts.
These should be kept as static as possible, as type-level as possible, and as mechanically checkable as possible.
Then declarative tests should supplement them.
Implementation should come last.</p>
<p>High Agenty belongs only where the law of the repository is already fixed, and where exploration inside that law is safe.</p>
<p>The point is not to surrender the whole repository to agents.
The point is to let architecture build the sandbox in which agents are allowed to be wild.</p>
<p>I think that is what design in the age of Coding Agents looks like.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Signals and Signals. And Retained UI.</title>
      <link>https://wtrclred.vercel.app/posts/12</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/12</guid>
      <description>Are signals just value primitives, or are they also a rendering architecture?</description>
      <pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-20T05:33:58.000Z</dc:date>
      <category>javascript</category>
      <category>oss</category>
      <category>signals</category>
      <category>reactivity</category>
      <category>vue</category>
      <category>react</category>
      <category>solid</category>
      <category>svelte</category>
      <category>vdom</category>
      <category>vapor</category>
      <content:encoded><![CDATA[<h1>Introduction</h1>
<p>Over the last few years, the word <strong>Signals</strong> has spread very quickly.
But it often points to two different things at the same time.</p>
<p>One meaning is the narrow one: signals as <strong>runtime reactive primitives</strong>, like <a href="https://vuejs.org/api/reactivity-core.html" target="_blank" rel="noopener noreferrer">Vue&#39;s <code>ref()</code></a> or <a href="https://docs.solidjs.com/reference/basic-reactivity/create-signal" target="_blank" rel="noopener noreferrer">Solid&#39;s <code>createSignal()</code></a>, which hold values and dependency relationships at runtime.
The other meaning is larger: signals as a <strong>rendering context that includes DOM tracking itself</strong>, as in <a href="https://svelte.dev/" target="_blank" rel="noopener noreferrer">Svelte</a>, <a href="https://github.com/solidjs/solid" target="_blank" rel="noopener noreferrer">Solid</a>, and <a href="https://github.com/vuejs/core/issues/13687" target="_blank" rel="noopener noreferrer">Vue Vapor</a>.</p>
<p>I think these two should be separated deliberately.
The former is reactivity of values.
The latter is reactivity of the renderer.</p>
<p>If we blur them together, we get lazy conclusions like <code>Signals = the end of VDOM</code>.
But that is not what the ecosystem actually says.
The <a href="https://github.com/tc39/proposal-signals" target="_blank" rel="noopener noreferrer">TC39 Signals proposal</a> explicitly says in its FAQ that signals can work with VDOM, with the native DOM, or with a combination of both.
So Signals are <strong>not a rendering strategy by themselves</strong>.</p>
<p>To talk about this properly, we need to go back a little.</p>
<h1>Knockout.js and the Observable era</h1>
<p>Whenever I see the current signals discourse, I think of <a href="https://knockoutjs.com/documentation/observables.html" target="_blank" rel="noopener noreferrer">Knockout.js</a>.
Knockout had <code>observable</code> and <code>computed</code> very early, with automatic dependency tracking.
And the <a href="https://knockoutjs.com/documentation/computed-dependency-tracking.html" target="_blank" rel="noopener noreferrer">Knockout docs</a> explain something crucial: <strong>declarative bindings themselves are implemented as computed observables</strong>.</p>
<p>That matters.
A lot of what we now call &quot;signal-like&quot; is not actually new.
Wrapping values, tracking dependencies, and updating part of the UI when those values change has been around for a long time.</p>
<p>For a while, though, the standards conversation focused less on signals and more on observables.
The original <a href="https://github.com/tc39/proposal-observable" target="_blank" rel="noopener noreferrer">TC39 Observable proposal</a> tried to standardize <strong>push-based streams of multiple values</strong>, such as DOM events, timers, and sockets.
Today the work continues as the <a href="https://github.com/WICG/observable" target="_blank" rel="noopener noreferrer">WICG Observable proposal</a>, and that repository explicitly documents the proposal&#39;s history: the 2015 TC39 attempt, the 2017 WHATWG DOM discussion, and the 2019 revival attempt.</p>
<p>It helps to draw a line here:</p>
<ul>
<li><p>Observables mainly model event streams over time.</p>
</li>
<li><p>Signals mainly model current values plus a dependency graph.</p>
</li>
</ul>
<p>They are related, but not identical.
Observables are primarily temporal.
Signals are primarily current-value oriented.</p>
<p>That is why the history of Observable standardization and the current <a href="https://github.com/tc39/proposal-signals" target="_blank" rel="noopener noreferrer">Signals proposal</a> are connected, but not interchangeable.</p>
<p>It is also notable that the Signals proposal is explicitly centered less on a developer-facing API and more on the <strong>core semantics of an underlying signal graph</strong> shared by frameworks.
Even here, Signals are already being framed as a substrate beneath frameworks, not merely as a userland convenience API.</p>
<h1>Signals and Signals.</h1>
<p>This is the core distinction I want to make.</p>
<p>The word &quot;signals&quot; is being used at at least two different scopes.</p>
<h2>1. Signals as runtime primitives</h2>
<p>This is the narrower meaning.</p>
<p><a href="https://vuejs.org/guide/extras/reactivity-in-depth.html" target="_blank" rel="noopener noreferrer">Vue&#39;s reactivity docs</a> explain that in Vue 3, <code>reactive()</code> uses Proxies while <code>ref()</code> uses getters and setters to implement <code>track()</code> and <code>trigger()</code>.
The same page also states that Vue&#39;s reactivity system is <strong>primarily runtime-based</strong>.</p>
<p><a href="https://docs.solidjs.com/reference/basic-reactivity/create-signal" target="_blank" rel="noopener noreferrer">Solid&#39;s <code>createSignal()</code> docs</a> describe a primitive where the getter tracks dependencies inside a reactive context and the setter notifies dependent computations.</p>
<p>At this layer, signals are something like this:</p>
<pre><code class="language-text">signal cell -&gt; track reads -&gt; trigger writes -&gt; rerun effects
</code></pre>
<p>The main character here is the <strong>value</strong>.
Which value was read?
Which effect depends on it?
What should be re-executed after mutation?</p>
<p>Vue <code>ref</code> and Solid <code>createSignal</code> are both easy to understand in this sense.</p>
<h2>2. Signals as a rendering architecture</h2>
<p>But there is another meaning.</p>
<p><a href="https://svelte.dev/" target="_blank" rel="noopener noreferrer">Svelte</a> describes itself as a UI framework that uses a compiler so the browser does minimal work.
The FAQ in the <a href="https://github.com/tc39/proposal-signals" target="_blank" rel="noopener noreferrer">TC39 Signals proposal</a> also points to Svelte 5 as an example where runes are transformed into an internal signals library.
The <a href="https://github.com/solidjs/solid" target="_blank" rel="noopener noreferrer">Solid README</a> shows compiled output that creates real DOM nodes and updates only the exact insertion point instead of rerendering the whole button.
And <a href="https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity" target="_blank" rel="noopener noreferrer">Solid&#39;s fine-grained reactivity docs</a> explicitly contrast targeted updates in Solid with broader component re-execution in React.</p>
<p>At that point, signals are no longer just value containers.
They become part of a context that also includes <strong>DOM-side tracking</strong>:
which signal feeds which text node, which attribute, which exact sink in the rendered output.</p>
<pre><code class="language-text">signal graph -&gt; compiler-known DOM sinks -&gt; exact DOM mutation
</code></pre>
<p>Now part of the renderer&#39;s job has been absorbed into the dependency graph itself.
The re-execution boundary is no longer naturally &quot;the whole component&quot;; it gets much closer to the concrete DOM sink.</p>
<p>In this sense, &quot;signals&quot; is not just the name of a primitive.
It is the name of an architectural stance.
Here I repeat the same word, <code>Signals</code>.</p>
<h1>Retained UI</h1>
<p>So where do VDOM and React Fiber fit?</p>
<p>I think of them as <strong>Retained UI</strong>.
That is: a runtime that first keeps the desired UI as nodes, frames, or trees in memory, and then manages updates from there.</p>
<p><a href="https://react.dev/learn/render-and-commit" target="_blank" rel="noopener noreferrer">React&#39;s Render and Commit docs</a> explain the sequence clearly:</p>
<ul>
<li><p>render calls components,</p>
</li>
<li><p>commit applies the result to the DOM,</p>
</li>
<li><p>and on re-render React computes what changed and applies only the minimal necessary DOM operations.</p>
</li>
</ul>
<p>Then <a href="https://github.com/acdlite/react-fiber-architecture" target="_blank" rel="noopener noreferrer">React Fiber architecture</a> goes deeper:</p>
<ul>
<li><p>rendering generates a tree in memory,</p>
</li>
<li><p>a new tree is diffed against the previous one,</p>
</li>
<li><p>Fiber is a unit of work, effectively a virtual stack frame specialized for React.</p>
</li>
</ul>
<p>This is the important part.
In React, the primary structure is not a signal dependency graph.
It is the <strong>in-memory tree / fiber graph</strong>.
State updates are the trigger, but the actual responsibility for DOM mutation belongs to the reconciler and the renderer.</p>
<p>In other words:</p>
<pre><code class="language-text">state update -&gt; rerun components -&gt; new in-memory tree -&gt; reconcile -&gt; commit DOM
</code></pre>
<p>That does not mean React has no reactivity.
It means reactivity is not foregrounded as a first-class dependency graph in the same way it is in Vue or Solid.
In React, reactivity mostly shows up as a question of <strong>which component subtree must be reevaluated</strong>, while Retained UI remains the main update engine.</p>
<h2>Vue has long sat in between</h2>
<p>What makes Vue interesting is that it has long contained both layers.</p>
<p><a href="https://vuejs.org/guide/extras/rendering-mechanism.html" target="_blank" rel="noopener noreferrer">Vue&#39;s Rendering Mechanism docs</a> explain that Vue:</p>
<ol>
<li><p>compiles templates into render functions,</p>
</li>
<li><p>performs mount as a reactive effect,</p>
</li>
<li><p>reruns that effect when dependencies change to create a new VDOM tree,</p>
</li>
<li><p>patches the DOM through reconciliation.</p>
</li>
</ol>
<p>The same page also explains <strong>compiler-informed virtual DOM</strong>, including patch flags and tree flattening.</p>
<p>So Vue has long had:</p>
<ul>
<li><p>a lower layer of reactivity via <code>ref</code>, <code>reactive</code>, and effects,</p>
</li>
<li><p>and an upper layer of compiler-assisted VDOM runtime.</p>
</li>
</ul>
<p>That is not merely &quot;between React and Solid&quot;.
It is more precise to say that Vue has long combined <strong>Signals and Retained UI</strong> in one system.</p>
<p>This is why Vue makes the bigger point visible:
signals do not inherently negate VDOM.
Signals can sit beneath VDOM.
Or they can sit beneath a more direct DOM-oriented renderer.</p>
<p>And again, the <a href="https://github.com/tc39/proposal-signals" target="_blank" rel="noopener noreferrer">TC39 Signals proposal</a> says exactly this: signals can back VDOM frameworks, native DOM frameworks, or hybrids.</p>
<h1>The future of Vue Vapor</h1>
<p>From this perspective, <a href="https://github.com/vuejs/core/issues/13687" target="_blank" rel="noopener noreferrer">Vue Vapor</a> becomes much easier to read.</p>
<p>The release note for <a href="https://github.com/vuejs/core/releases/tag/v3.6.0-beta.1" target="_blank" rel="noopener noreferrer">Vue 3.6.0-beta.1, published on December 23, 2025</a>, describes Vapor Mode as:</p>
<ul>
<li><p>a new compilation mode for Vue SFCs,</p>
</li>
<li><p>aimed at reducing baseline bundle size and improving performance,</p>
</li>
<li><p>100% opt-in,</p>
</li>
<li><p>feature-complete for the intended subset, but still unstable.</p>
</li>
</ul>
<p>But the really important details are the interop details.
That same release note says:</p>
<ol>
<li><p>Vapor-only mode does not support Suspense directly, but <strong>Vapor components can be rendered inside a VDOM Suspense</strong></p>
</li>
<li><p>a VDOM app created with <code>createApp</code> can use Vapor components through <strong><code>vaporInteropPlugin</code></strong></p>
</li>
<li><p>a Vapor app can also use VDOM components through the same interop path</p>
</li>
<li><p>mixed nesting still has rough edges, so Vue recommends <strong>distinct regions</strong></p>
</li>
</ol>
<p>That is very revealing.
At least in the near future, Vue Vapor is not &quot;the next Vue that fully replaces VDOM.&quot;
It looks much more like a <strong>high-performance subset</strong> that grows through interoperability with the existing Vue runtime.</p>
<p>The public <a href="https://github.com/vuejs/core/issues/13687" target="_blank" rel="noopener noreferrer">Vapor Roadmap</a> reinforces this.
It includes work around:</p>
<ul>
<li><p>SSR / Hydration</p>
</li>
<li><p>Template Ref Interop</p>
</li>
<li><p>Suspense</p>
</li>
<li><p>Vue Router</p>
</li>
<li><p>Pinia</p>
</li>
<li><p>Nuxt.js</p>
</li>
<li><p>DevTools Integration</p>
</li>
<li><p>Vue Test Utils</p>
</li>
</ul>
<p>That tells us something simple.
The fundamental challenge of Vapor is not merely &quot;can it update the DOM faster?&quot;
The challenge is <strong>how it fits the Vue ecosystem as a whole</strong>.</p>
<p>So my reading of Vue Vapor&#39;s future is this:</p>
<ul>
<li><p>VDOM is not just leftover legacy weight</p>
</li>
<li><p>Vapor becomes the high-performance subset for perf-sensitive paths</p>
</li>
<li><p>the VDOM runtime remains important for interop, ecosystem breadth, and library compatibility</p>
</li>
<li><p>and the boundary between the two becomes more explicit over time</p>
</li>
</ul>
<p>That feels very Vue-like to me.
Vue has always cared about progressive and incremental adoption.
If Vapor succeeds, I suspect it will succeed less by replacing everything and more by establishing better architectural boundaries.</p>
<h1>Why this matters in comparison with React</h1>
<p>In React, Retained UI is closer to the core than value-level reactivity.
Fiber handles scheduling, reconciliation, prioritization, and work management.</p>
<p>By contrast, the direction of Vapor, Svelte, and the Solid compiler is to bring dependency tracking and DOM sink tracking closer together, reducing how much they need an in-memory tree as the primary update mechanism.</p>
<p>The comparison should not be &quot;which one wins.&quot;
The real comparison is: <strong>which layer owns update responsibility?</strong></p>
<ul>
<li><p>React / classic VDOM: the in-memory node runtime owns updates</p>
</li>
<li><p>runtime signals: the signal graph owns invalidation, but the renderer is still separate</p>
</li>
<li><p>compiler + DOM-tracker signals: the signal graph begins to absorb part of the renderer&#39;s responsibility</p>
</li>
</ul>
<p>These are not the same thing.</p>
<p>And Vue, right now, is trying to carry all of them in one system.
<code>ref</code> remains.
VDOM mode remains.
And Vapor grows on top of that.</p>
<p>I think that is a healthy direction.</p>
<p>Signals are not a magic silver bullet.
And VDOM is not merely a relic of the past.
Retained UI is still strong at component composition, dynamic rendering, ecosystem interop, debuggability, and tooling alignment.
At the same time, compiler-guided <code>Signals.</code> are strong on fine-grained hot-path updates and baseline bundle size.</p>
<p>The question is almost never &quot;which one should disappear?&quot;
The real question is &quot;where should the boundary be?&quot;</p>
<hr>
<p>This is a personal essay, not an official position of the Vue Team, the React Team, or TC39.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Is React Really UI = f(State)?</title>
      <link>https://wtrclred.vercel.app/posts/09</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/09</guid>
      <description>What purity and idempotence mean - reading React more carefully through category theory, algebraic effects, and Applicative structure</description>
      <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T08:53:41.000Z</dc:date>
      <category>react</category>
      <category>javascript</category>
      <category>oss</category>
      <category>category-theory</category>
      <category>algebraic-effects</category>
      <category>applicative</category>
      <category>mathematics</category>
      <category>semantics</category>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Note</strong>: I am not a mathematics specialist. This essay is an attempt to use the vocabulary of category theory, algebraic effects, and Applicative structure to understand React, so please read it as a proposed reading rather than a rigorous theorem. If you notice a mathematical mistake, I would be happy to be corrected.</p>
</blockquote>
<h1>Introduction</h1>
<p>&quot;UI = f(State)&quot; - this is one of the most widely circulated equations in discussions about React.
At first glance, the equation looks simple and seems to capture the essence of React: if the state is determined, the UI is determined.</p>
<p>However, when we read this equation carefully in mathematical terms, several points need clarification.</p>
<ul>
<li><p>What is this <code>f</code>? Is it a &quot;function&quot; in the mathematical sense?</p>
</li>
<li><p>If it is a function, in which category is it a morphism?</p>
</li>
<li><p>Are React&#39;s notions of &quot;pure&quot; and &quot;idempotent&quot; the same as the mathematical ones?</p>
</li>
</ul>
<p>In this essay, I will use the vocabulary of category theory, algebraic effects, and Applicative structure to try to read React Components more precisely.</p>
<hr>
<h1>The Mathematical Meaning of &quot;Pure&quot;</h1>
<p>Let us clarify the mathematical meaning of the word &quot;pure,&quot; which is often used in React documentation and discussions.</p>
<p>In mathematics, a <strong>pure function</strong> is a mapping with referential transparency.
In the language of category theory, it corresponds to a morphism in the category of sets, <strong>Set</strong>:</p>
<p>$$f: A \to B$$</p>
<p>This morphism <code>f</code> satisfies the following:</p>
<ul>
<li><p>It always returns the same output for the same input (deterministic)</p>
</li>
<li><p>It does not read from or write to external state (no side effects)</p>
</li>
<li><p>It does nothing other than compute a value (no observable effects)</p>
</li>
</ul>
<p>Now consider a React Function Component:</p>
<pre><code class="language-jsx">function Greeting({ name }) {
  const [count, setCount] = useState(0);
  const theme = useContext(ThemeContext);

  useEffect(() =&gt; {
    document.title = `Hello, ${name}`;
  }, [name]);

  return (
    &lt;h1 style={{ color: theme.primary }}&gt;
      Hello, {name} ({count})
    &lt;/h1&gt;
  );
}
</code></pre>
<p>This Component:</p>
<ul>
<li><p>reads internal state and receives an updater through <code>useState</code></p>
</li>
<li><p>reads external state from the component tree through <code>useContext</code></p>
</li>
<li><p>schedules a side effect through <code>useEffect</code></p>
</li>
</ul>
<p>This is not a morphism in <strong>Set</strong>.
It reads things other than its argument (props), and it does things other than compute a pure value.</p>
<p><strong>A React Component is not a &quot;pure function&quot; in the mathematical sense.</strong></p>
<p>Then why does React call Components &quot;pure&quot;?
Because React uses the word &quot;pure&quot; in <strong>a different sense</strong>.
In React, &quot;pure&quot; means:</p>
<blockquote>
<p>Having no observable side effects during rendering - more precisely, behaving correctly inside React&#39;s effect system.</p>
</blockquote>
<p>This is a much weaker condition than mathematical purity, and confusing these terms leads to a fundamental misunderstanding.</p>
<hr>
<h1>The Original Meaning of &quot;Idempotent&quot;</h1>
<p>React&#39;s documentation says the following (<a href="https://react.dev/reference/rules/components-and-hooks-must-be-pure" target="_blank" rel="noopener noreferrer">Components and Hooks must be pure</a>):</p>
<blockquote>
<p>&quot;Components must be idempotent - React components are assumed to always return the same output with respect to their inputs - props, state, and context.&quot;</p>
</blockquote>
<p>What does &quot;idempotent&quot; mean mathematically here?</p>
<h2>Idempotence in Mathematics</h2>
<p>The definition of an idempotent element in algebra is:</p>
<blockquote>
<p><strong>Definition (idempotent element).</strong> In a monoid <code>(M, ·, e)</code>, an element <code>a ∈ M</code> is <strong>idempotent</strong> if it satisfies <code>a · a = a</code>.</p>
</blockquote>
<p>Extending this to functions:</p>
<blockquote>
<p><strong>Definition (idempotent endomorphism).</strong> In a category <code>𝒞</code>, a morphism <code>e: A → A</code> is <strong>idempotent</strong> if it satisfies <code>e ∘ e = e</code>.</p>
</blockquote>
<p>These two definitions follow <a href="https://mathworld.wolfram.com/Idempotent.html" target="_blank" rel="noopener noreferrer">Eric W. Weisstein, &quot;Idempotent,&quot; <em>MathWorld</em></a> for idempotent elements and <a href="https://emilyriehl.github.io/files/context.pdf" target="_blank" rel="noopener noreferrer">Emily Riehl, <em>Category Theory in Context</em>, Example 3.2.14</a> for idempotent endomorphisms.</p>
<p>Concrete examples:</p>
<ul>
<li><p>The absolute value function is idempotent: <code>||x|| = |x|</code></p>
</li>
<li><p>A projection matrix <code>P</code> is idempotent: <code>P^2 = P</code></p>
</li>
<li><p><code>Math.floor</code> is idempotent: <code>Math.floor(Math.floor(x)) === Math.floor(x)</code></p>
</li>
</ul>
<p>What matters here is that <strong>idempotence is a property of an endomorphism <code>f: A -&gt; A</code></strong>.
Applying <code>f</code> once more to the output of <code>f</code> does not change the result - this is the original meaning of idempotence.</p>
<h2>React&#39;s &quot;Idempotence&quot; Is Not Mathematical Idempotence</h2>
<p>When React says a Component is &quot;idempotent,&quot; it means &quot;it returns the same output for the same input.&quot;
Mathematically, this is not idempotence.
It should be called <strong>determinism</strong> or <strong>well-definedness</strong>.</p>
<p>Why?</p>
<ol>
<li><p>The type of a Component is <code>Props -&gt; VDOM</code>, and <code>Props != VDOM</code>. In other words, it is not even an endomorphism.</p>
</li>
<li><p>To verify <code>f ∘ f = f</code>, we need to pass the output of <code>f</code> back into <code>f</code> as input. But passing a Component&#39;s output (VDOM) into the Component&#39;s input (Props) has no meaning.</p>
</li>
<li><p>What React is saying is &quot;<code>f(x) = f(x)</code> (the same input gives the same output),&quot; not &quot;<code>f(f(x)) = f(x)</code>.&quot;</p>
</li>
</ol>
<p><code>f(x) = f(x)</code> is a property every function should satisfy by definition, and it is not something we would go out of our way to call &quot;idempotence.&quot; More precisely, what React intends is probably that &quot;even if Hooks have internal state, the Component returns the same VDOM for the same tuple of (props, state, context).&quot; But this is a &quot;deterministic function,&quot; not an &quot;idempotent&quot; one.</p>
<h2>The Rendering Operation Can Be Expressed as Idempotent</h2>
<p>However, if we shift our point of view, React does contain an operation that can be expressed as idempotent.</p>
<p>Fix a state <code>s</code>, set aside user Effects and external I/O for the moment, and look only at the DOM state.
Then consider the composition of rendering and committing as a single operation:</p>
<p>$$
\begin{array}{c}
u_{s}: \mathrm{DOM} \to \mathrm{DOM}
\\
u_{s} = \operatorname{commit}(\operatorname{reconcile}(\operatorname{render}(s)))
\end{array}
$$</p>
<p>Then:</p>
<p>$$u_{s}(u_{s}(\operatorname{dom})) = u_{s}(\operatorname{dom})$$</p>
<p>Applying render and commit twice for the same state yields the same DOM state as applying them once, if we look only at the DOM state.
If there is no diff, the second application can be treated as a no-op.
<strong>This is idempotence when viewed as a DOM state transformation.</strong></p>
<p>In other words, what should be treated as idempotent in React is not <strong>the Component function itself</strong>, but the idealized <strong>render-and-commit operation</strong> <code>u_s</code>.
This distinction is decisive.</p>
<hr>
<h1>React Fiber and Algebraic Effects</h1>
<p>Now let us turn to React&#39;s internal structure.</p>
<p>React Fiber - React&#39;s core runtime - has features that resemble Algebraic Effects.
Dan Abramov also introduces algebraic effects as a way to think about some things React does (<a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/" target="_blank" rel="noopener noreferrer">Dan Abramov, &quot;Algebraic Effects for the Rest of Us&quot;</a>, 2019). The same article explicitly calls the connection to React &quot;a stretch,&quot; says Suspense is not an algebraic effect per se, and notes that JavaScript cannot really resume execution later.</p>
<h2>What Are Algebraic Effects?</h2>
<p>Algebraic effects are a mathematical framework for separating computation from effects (side effects).
The general explanation in this section follows <a href="https://doi.org/10.1023/A:1023064908962" target="_blank" rel="noopener noreferrer">Plotkin and Power, &quot;Algebraic Operations and Generic Effects&quot;</a> (2003).</p>
<p>The basic structure consists of three elements:</p>
<ol>
<li><p><strong>Effect signature</strong>: the set of available operations</p>
</li>
<li><p><strong>Computation</strong>: a program that may call effect operations</p>
</li>
<li><p><strong>Handler</strong>: an interpreter that gives meaning to effect operations</p>
</li>
</ol>
<p>This is not React&#39;s actual implementation, but pseudocode for explaining the correspondence:</p>
<pre><code>// Effect signature
effect GetState  : Unit -&gt; State
effect SetState  : State -&gt; Unit
effect ReadProps : Unit -&gt; Props
effect ReadContext: Key -&gt; Value
effect Suspend   : Promise&lt;A&gt; -&gt; A
effect Throw     : Error -&gt; bottom

// Handler (= React Fiber runtime)
handler ReactFiber {
  return vdom -&gt; vdom
  ReadProps(_, resume)      -&gt; resume(currentProps)
  GetState(_, resume)       -&gt; resume(currentFiber.memoizedState)
  SetState(newState, resume) -&gt; enqueueUpdate(newState); resume(unit)
  Suspend(promise, retryRender) -&gt; showFallback(); promise.then(() -&gt; retryRender())
  Throw(error, _)           -&gt; propagateToErrorBoundary(error)
}
</code></pre>
<p>Here the relationship between <strong>the React Function Component written by the user</strong> and <strong>the React Fiber runtime</strong> can be mapped as follows:</p>
<table>
<thead>
<tr>
<th></th>
<th>Role in algebraic effects</th>
<th>Correspondence in React</th>
</tr>
</thead>
<tbody>
<tr>
<td>Effect signature</td>
<td>Declaration of available operations</td>
<td>Hooks API (<code>useState</code>, <code>useContext</code>, <code>use</code>, ...)</td>
</tr>
<tr>
<td>Computation</td>
<td>Program that calls effect operations</td>
<td><strong>User-written Function Component</strong></td>
</tr>
<tr>
<td>Handler</td>
<td>Interpreter that gives meaning to operations</td>
<td><strong>React Fiber runtime</strong></td>
</tr>
</tbody>
</table>
<p>In this correspondence, React Hooks can be read as effect operations:</p>
<table>
<thead>
<tr>
<th>Hook</th>
<th>Effect operation</th>
<th>Handler</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useProps</code></td>
<td><code>ReadProps</code></td>
<td>Current props</td>
</tr>
<tr>
<td><code>useState</code></td>
<td><code>GetState / SetState</code></td>
<td>Fiber&#39;s state queue</td>
</tr>
<tr>
<td><code>useContext</code></td>
<td><code>ReadContext</code></td>
<td>Provider chain lookup</td>
</tr>
<tr>
<td><code>use(promise)</code></td>
<td><code>Suspend</code></td>
<td>Suspense boundary</td>
</tr>
<tr>
<td><code>throw error</code></td>
<td><code>Throw</code></td>
<td>Error boundary</td>
</tr>
<tr>
<td><code>useEffect</code></td>
<td><code>ScheduleEffect</code></td>
<td>Effect queue in commit phase</td>
</tr>
</tbody>
</table>
<p>Here <code>useProps</code> is not a real React API. In the rest of this essay, I will generalize props - normally received as a Component function argument - as a conceptual Hook operation that reads the current props from the handler.</p>
<p><strong>A user-written Component can be viewed as an effectful computation, and React Fiber as its handler (interpreter).</strong></p>
<p>However, this is not a claim that algebraic effects alone explain all of React. In particular, the static shape of the Hook call sequence required by the Rules of Hooks is hard to capture using only ordinary monads or algebraic effects. The order should be: the Rules of Hooks come first, constraining the shape of the Component body; as a result, the Hook call sequence can be embedded fairly faithfully into an Applicative / Free Applicative-like structure.</p>
<hr>
<h1>Function Composition in the Kleisli Category</h1>
<p>The &quot;function composition&quot; implicitly assumed by &quot;UI = f(State)&quot; also needs to be examined mathematically.</p>
<h2>Kleisli Category</h2>
<p>Given a monad <code>T</code> on a category <code>𝒞</code>, we can construct the <strong>Kleisli category</strong> <code>𝒞_T</code>.
Here I mean a categorical monad: an endofunctor <code>T: 𝒞 → 𝒞</code> together with natural transformations <code>η: Id ⇒ T</code> (unit) and <code>μ: T² ⇒ T</code> (multiplication), forming a triple <code>(T, η, μ)</code> that satisfies the monad laws. For the definitions of monad and Kleisli category, see <a href="https://emilyriehl.github.io/files/context.pdf" target="_blank" rel="noopener noreferrer">Emily Riehl, <em>Category Theory in Context</em>, Definition 5.1.1 and Definition 5.2.10</a>:</p>
<ul>
<li><p><strong>Objects</strong>: the same as <code>𝒞</code></p>
</li>
<li><p><strong>Morphisms</strong>: a morphism <code>A -&gt; B</code> in <code>𝒞_T</code> is a morphism <code>A -&gt; T(B)</code> in <code>𝒞</code></p>
</li>
<li><p><strong>Composition</strong>: for <code>f: A -&gt; T(B)</code> and <code>g: B -&gt; T(C)</code>, their Kleisli composition is:</p>
</li>
</ul>
<p>$$g \circ_{T} f = \mu_{C} \circ T(g) \circ f$$</p>
<p>That is:</p>
<p>$$
A \xrightarrow{f} T(B) \xrightarrow{T(g)} T(T(C)) \xrightarrow{\mu_{C}} T(C)
$$</p>
<h2>A React Component Is a Kleisli Morphism</h2>
<p>From this point on, this is not an official formal semantics of React.
It is a sketch for reading React&#39;s different effects through one abstract effect monad.
Let <code>R</code> be that hypothetical React effect monad.
If we follow the surface syntax of JavaScript, the type of a React Component can be read as:</p>
<p>$$\mathrm{Component}: \mathrm{Props} \to R(\mathrm{VDOM})$$</p>
<p>This is a way to read it as a morphism <code>Props -&gt; VDOM</code> in the Kleisli category <code>Set_R</code>. However, if we want to handle the program structure of Hook operations uniformly, props can also be moved inside the computation as a conceptual Hook operation:</p>
<p>$$\operatorname{useProps}: R(\mathrm{Props})$$</p>
<p>In this reading, <code>Props -&gt; R(VDOM)</code> is the expression where props are supplied first, while <code>R(VDOM)</code> with <code>useProps</code> is the expression where React Fiber supplies props during interpretation.</p>
<p><strong>Reading it as a Kleisli morphism (an effectful computation), rather than as a Set morphism (a pure function), is closer to React&#39;s behavior.</strong></p>
<h2>Component Composition</h2>
<p>Consider component composition in JSX:</p>
<pre><code class="language-jsx">function Parent({ data }) {
  const processed = use(processData(data));
  return &lt;Child items={processed} /&gt;;
}
</code></pre>
<p>If we abstract this dependency, we can sketch it as Kleisli composition:</p>
<p>$$\mathrm{Parent} = \mathrm{Child} \circ_{R} \mathrm{process}$$</p>
<p>Here <code>∘_R</code> is not ordinary composition <code>∘</code> in <strong>Set</strong>, but <strong>Kleisli composition</strong> for the abstracted React effect monad.
Because it passes through a call to <code>use</code> - that is, an effect corresponding to Suspend - it is hard to express as pure function composition.</p>
<p>In other words, if we include Hooks and Suspense, &quot;function composition&quot; in React cannot be fully captured by ordinary function composition alone.
This is another reason why &quot;UI = f(State)&quot; is not naively correct.</p>
<h2>Monad Bind and an Applicative Skeleton - What a Component Body Really Is</h2>
<p>Now that we understand Kleisli composition, let us dig one layer deeper into <strong>what is happening inside the function body of a Component</strong>.</p>
<p>For a monad <code>T</code>, the <strong>bind operation</strong> (written <code>&gt;&gt;=</code> in Haskell) has the following type:</p>
<p>$$\operatorname{bind}_{T}: T(A) \times (A \to T(B)) \to T(B)$$</p>
<p>It is the operation that &quot;takes the result of an effectful computation and passes it to the next effectful computation.&quot;
Haskell&#39;s <strong>do notation</strong> is syntactic sugar for writing chains of bind in a readable way:</p>
<pre><code class="language-haskell">-- do notation
do
  state &lt;- getState
  ctx   &lt;- readContext themeCtx
  pure (view state ctx)

-- Desugared (chain of bind)
getState    &gt;&gt;= \state -&gt;
readContext themeCtx &gt;&gt;= \ctx -&gt;
pure (view state ctx)
</code></pre>
<p>This perspective is useful when reading a React Component body as a computation that calls effects in order and returns VDOM from their results. If we also include <code>useProps</code> in the abstraction, for example:</p>
<pre><code class="language-jsx">function Component() {
  const props = useProps(); // conceptual Hook operation: ReadProps &gt;&gt;= \props -&gt;
  const [state, setState] = useState(init); // &lt;- bind: GetState &gt;&gt;= \state -&gt;
  const ctx = useContext(ThemeCtx); // &lt;- bind: ReadContext &gt;&gt;= \ctx -&gt;
  return &lt;View name={props.name} state={state} ctx={ctx} /&gt;; // &lt;- pure: return (View props state ctx)
}
</code></pre>
<p><strong>Locally, the body of a React Component can be read as something close to do notation for the React effect monad.</strong></p>
<p>There is an important caveat, though. <strong>An ordinary monad is too expressive to represent React&#39;s requirement that the Hook call sequence have a static shape.</strong> With bind, the result of an earlier computation can decide the very shape of the effects that run next:</p>
<pre><code class="language-haskell">do
  x &lt;- action1
  if condition x
    then do { y &lt;- action2; pure (f x y) }  -- action2 is present
    else pure (g x)                          -- action2 is absent
</code></pre>
<p>This is natural to write as monadic computation. But React Hooks specifically forbid changing the shape of the Hook sequence based on values or branches.</p>
<p>The property &quot;the shape of the effects is known first, and only the values flow in later&quot; is closer to <strong>Applicative</strong> than to Monad. An Applicative <code>F</code> roughly has these operations:</p>
<p>$$
\begin{array}{c}
\operatorname{pure}_{F}: A \to F(A)
\\
\operatorname{ap}_{F}: F(A \to B) \times F(A) \to F(B)
\end{array}
$$</p>
<p>Here <code>ap_F</code> corresponds to <code>&lt;*&gt;</code> in Haskell.</p>
<p>An Applicative does not have a bind-like <code>F(A) -&gt; (A -&gt; F(B)) -&gt; F(B)</code>. Therefore, the result of a previous effect cannot choose the structure of later effects. Instead, <strong>the skeleton of which effects are used, and in which order, is fixed statically, and their results are fed into a pure function</strong>.</p>
<p>For a Component that satisfies the Rules of Hooks, we can very roughly sketch the Hook call sequence in an Applicative-like form:</p>
<pre><code class="language-haskell">pure view &lt;*&gt; useProps &lt;*&gt; useState init &lt;*&gt; useContext themeCtx
</code></pre>
<p>This is not React&#39;s implementation. But once props are also included as the conceptual Hook <code>useProps</code>, an Applicative skeleton is closer than a chain of bind as a structural sketch of &quot;the input and Hook sequence are fixed, and JSX is constructed from the values obtained from them.&quot;</p>
<h3>The Rules of Hooks Make an Applicative Skeleton Possible</h3>
<p>Now reconsider React&#39;s <a href="https://react.dev/reference/rules/rules-of-hooks" target="_blank" rel="noopener noreferrer"><strong>Rules of Hooks</strong></a>:</p>
<blockquote>
<ul>
<li><p>Do not call Hooks inside loops, conditions, or nested functions</p>
</li>
<li><p>Hooks must always be called at the top level of a Function Component</p>
</li>
</ul>
</blockquote>
<p>The order matters here. The rule comes first. Rather than saying that &quot;the Applicative expression explains the Rules of Hooks,&quot; it is better to say that <strong>the Rules of Hooks constrain the shape of the Component body enough that it can be embedded into an Applicative skeleton of Hook operations, including <code>useProps</code></strong>.</p>
<p>By &quot;faithful embedding&quot; here, I only mean that the number and order of Hook calls can be extracted as program structure without being lost.</p>
<p>The React equivalent is <strong>forbidden</strong>:</p>
<pre><code class="language-jsx">// NG: Hook inside a conditional branch
function Component({ condition }) {
  const [x] = useState(0);
  if (condition) {
    const y = useContext(Ctx); // &lt;- Rules of Hooks violation
    return &lt;A x={x} y={y} /&gt;;
  }
  return &lt;B x={x} /&gt;;
}
</code></pre>
<p>Why?
React Fiber stores the order of Hook calls as a linked list.
If the number or order of Hook operations changes from render to render, Fiber cannot correctly line up the previous render&#39;s result with the current Hook calls.</p>
<p>On the other hand, it is fine to fix the Hook sequence first and then branch in the UI returned from the resulting values:</p>
<pre><code class="language-jsx">// OK: the Hook sequence is fixed
function Component({ condition }) {
  const [x] = useState(0);
  const y = useContext(Ctx);
  return condition ? &lt;A x={x} y={y} /&gt; : &lt;B x={x} /&gt;;
}
</code></pre>
<p>In other words, what React needs to keep static is not &quot;the rendering result that uses values,&quot; but <strong>the program structure of Hook operations</strong>. Because the Rules of Hooks fix that shape first, it becomes possible to map it into a structure such as <code>FreeApplicative HookOp</code>, or, if we also include the recursion of the component tree, something like <code>Fix (FreeApplicative HookOp)</code>.</p>
<p>A related experimental implementation is <a href="https://github.com/ubugeeei/mreact" target="_blank" rel="noopener noreferrer">ubugeeei/mreact</a>.
It expresses React Hooks as an indexed monad in Haskell, representing the Hook call order as a type-level list so that the Rules of Hooks can be handled by the type checker. In the framing of this essay, that type-level Hook list is better understood not as an expression attached to React after the fact, but as a capture of the program shape that the Rules of Hooks already force: a static skeleton closer to Applicative / Free Applicative structure.</p>
<hr>
<h1><code>use</code> and <code>Suspense</code> - A Concrete Form That Resembles Algebraic Effects</h1>
<p>The <code>use</code> Hook and <code>Suspense</code> are where the algebraic-effects-like behavior in React is easiest to see.
However, this does not mean that React&#39;s implementation has real algebraic effects.</p>
<pre><code class="language-jsx">function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return &lt;div&gt;{user.name}&lt;/div&gt;;
}

function App() {
  return (
    &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
      &lt;UserProfile userId={1} /&gt;
    &lt;/Suspense&gt;
  );
}
</code></pre>
<p>We can describe this mechanism with the vocabulary of algebraic effects:</p>
<p><strong>1. <code>use(promise)</code> corresponds to performing an effect operation:</strong></p>
<p>If the Promise is unresolved, the public React API says that the Component <strong>suspends</strong> (<code>use</code> integrates with <a href="https://react.dev/reference/react/use" target="_blank" rel="noopener noreferrer">Suspense</a>).
This can be read as a non-local control flow resembling a &quot;call to an operation&quot; in algebraic effects.</p>
<p><strong>2. <code>Suspense</code> corresponds to an effect handler:</strong></p>
<p><code>Suspense</code> handles the suspension, displays the fallback UI, and retries rendering after the Promise resolves.</p>
<p><strong>3. On re-render, <code>use(promise)</code> returns the resolved value:</strong></p>
<p>This resembles the algebraic-effects operation of &quot;passing a value to the continuation and resuming it.&quot;
However, React is not actually saving the JavaScript execution stack and resuming from that point.
It re-renders the Component tree after the Promise resolves.
This point follows the <a href="https://react.dev/reference/react/Suspense" target="_blank" rel="noopener noreferrer">React Suspense documentation</a> and Dan Abramov&#39;s caveat in <a href="https://overreacted.io/algebraic-effects-for-the-rest-of-us/" target="_blank" rel="noopener noreferrer">&quot;Algebraic Effects for the Rest of Us&quot;</a>.</p>
<p>In pseudo handler notation:</p>
<pre><code>handle(UserProfile(userId)) with {
  return vdom -&gt; vdom
  Suspend(promise, retryRender) -&gt;
    display &lt;Loading /&gt;;
    await promise;
    retryRender()  // &lt;- re-render, not a real continuation resume
}
</code></pre>
<p>What is especially important here is that <strong><code>use</code> can suspend a Component&#39;s render, and React can re-render it later</strong>.</p>
<p>An ordinary JavaScript function runs to completion once called.
With <code>async</code> / <code>await</code>, execution can continue from an <code>await</code> point, but React Components are not written as <code>async function</code>.
Even so, React uses Suspense boundaries and re-rendering to create the experience that it &quot;waited there and continued once the value arrived.&quot;
This is what makes the behavior reminiscent of algebraic effects (or delimited continuations).</p>
<hr>
<h1>The Mistake of Client-Server &quot;Isomorphism&quot;</h1>
<p>There are terms such as &quot;Isomorphic JavaScript&quot; and &quot;Universal JavaScript.&quot;
They are also often used in the context of Server Components, but is this &quot;isomorphism&quot; mathematically correct?</p>
<h2>Isomorphism in Category Theory</h2>
<blockquote>
<p><strong>Definition (isomorphism).</strong> In a category <code>𝒞</code>, a morphism <code>f: A → B</code> is an <strong>isomorphism</strong> if there exists a morphism <code>g: B → A</code> such that <code>g ∘ f = id_A</code> and <code>f ∘ g = id_B</code>.</p>
</blockquote>
<p>This definition follows <a href="https://emilyriehl.github.io/files/context.pdf" target="_blank" rel="noopener noreferrer">Emily Riehl, <em>Category Theory in Context</em>, Definition 1.1.10</a>.</p>
<p>An isomorphism means a reversible transformation that preserves structure.
If <code>A</code> and <code>B</code> are isomorphic, then they cannot be distinguished category-theoretically.</p>
<h2>Server Rendering and Client Rendering Are Not Isomorphic</h2>
<p>Consider server rendering and client rendering as functors:</p>
<p>$$
\begin{array}{c}
F_{S}: \mathrm{Component} \to \mathrm{HTML}
\\
F_{C}: \mathrm{Component} \to \mathrm{DOM}
\end{array}
$$</p>
<p>To begin with, <code>HTML != DOM</code>.
Since the output categories are different, there is no room to construct an isomorphism between <code>F_S</code> and <code>F_C</code>.</p>
<p>A transformation from an HTML string to the DOM (parsing) exists, but the reverse transformation (DOM -&gt; HTML) is serialization.
The composition of these two is not necessarily the identity morphism, because event handlers, internal state, closures, and so on are lost.</p>
<h2>A More Accurate Description: Different Effect Handlers</h2>
<p>In the framework of algebraic effects, server rendering and client rendering can be understood as <strong>different handlers for the same effectful computation</strong>:</p>
<p>$$
\begin{array}{c}
\mathrm{Component}: \mathrm{Props} \to \mathrm{Eff}_{\mathrm{React}}(\mathrm{VDOM})
\\
\mathrm{handleServer}: \mathrm{Eff}_{\mathrm{React}}(\mathrm{VDOM}) \to \mathrm{HTMLString}
\\
\mathrm{handleClient}: \mathrm{Eff}_{\mathrm{React}}(\mathrm{VDOM}) \to \mathrm{DOMMutations}
\end{array}
$$</p>
<p>As the <a href="https://react.dev/reference/rsc/use-client" target="_blank" rel="noopener noreferrer">React documentation</a> explains, the difference between Server Components and Client Components appears as a difference in execution environment and available APIs.
In this essay&#39;s reading, we can understand that as <strong>a difference in available effect signatures</strong>:</p>
<ul>
<li><p><strong>Server Component</strong>: can directly use server-side operations such as DB queries and file system access. Cannot use many Hooks such as <code>useState</code> and <code>useEffect</code>.</p>
</li>
<li><p><strong>Client Component</strong>: can use client-side APIs such as <code>useState</code> and <code>useEffect</code>. Cannot directly bring server-only code into the Client module subtree.</p>
</li>
</ul>
<p>This is not an isomorphism.
It should be understood as an <strong>inclusion relation</strong> or <strong>subtyping</strong> between effect signatures.</p>
<p>$$
\begin{array}{c}
\mathrm{ServerEffects} \neq \mathrm{ClientEffects}
\\
\mathrm{SharedEffects} = \mathrm{ServerEffects} \cap \mathrm{ClientEffects}
\end{array}
$$</p>
<p>The shared portion works as Components that can become either Server Components or Client Components depending on usage.
Server-only operations are available only on the server side, and client-only operations are available only on the client side.
This is not &quot;isomorphism,&quot; but <strong>compatibility</strong> based on partial overlap between effect signatures.</p>
<hr>
<h1>Conclusion: Rewriting UI = f(State)</h1>
<p>Let us summarize the discussion so far.</p>
<h2>Problems with the Naive Equation</h2>
<p>$$\mathrm{UI} = f(\mathrm{State})$$</p>
<p>Problems with this equation:</p>
<ol>
<li><p><code>f</code> is not a pure function - it executes effects through Hooks</p>
</li>
<li><p>The word &quot;idempotent&quot; is misused - the Component function is not mathematically idempotent (the rendering operation can be expressed as idempotent)</p>
</li>
<li><p>Composition cannot be captured by ordinary function composition alone - effect interpretation can be expressed as Kleisli composition, while the Hook sequence fixed by the Rules of Hooks is more naturally viewed as Applicative structure</p>
</li>
<li><p>Client-Server is not an isomorphism - they can be expressed as different effect handlers</p>
</li>
</ol>
<h2>A More Accurate Description</h2>
<p>In this essay&#39;s reading, at the level of JavaScript&#39;s surface syntax, a React Component can be expressed as a Kleisli morphism of the effect monad <code>R</code>:</p>
<p>$$\mathrm{Component}: \mathrm{Props} \to R(\mathrm{VDOM})$$</p>
<p>However, if we want to handle the program structure of Hook operations uniformly, props should be moved inside as a conceptual Hook operation rather than kept only as an external argument:</p>
<p>$$\operatorname{useProps}: R(\mathrm{Props})$$</p>
<p>In that case, the equation above captures the aspect that a Component is an effectful computation interpreted by the React runtime. It does not by itself capture the static Hook sequence required by the Rules of Hooks. For the program structure of Hook operations, the Rules of Hooks fix the shape first. Because of that constraint, the structure can be mapped into an Applicative / Free Applicative-like skeleton that also includes <code>ReadProps</code>:</p>
<p>$$\mathrm{HookProgram}\ A \approx \mathrm{FreeApplicative}(\mathrm{HookOp})\ A$$</p>
<p>UI generation can be expressed as interpretation by an effect handler <code>h</code>:</p>
<p>$$\mathrm{UI} = h_{\mathrm{props}}(\mathrm{ComponentProgram})$$</p>
<p>Here:</p>
<ul>
<li><p><code>ComponentProgram: HookProgram(VDOM)</code> is the <strong>description</strong> of the effectful computation written by the user, including <code>useProps</code>, <code>useState</code>, <code>useContext</code>, and so on</p>
</li>
<li><p><code>h_props: HookProgram(VDOM) -&gt; DOM</code> is an expression of the React Fiber runtime interpreting that computation while supplying the current props</p>
</li>
</ul>
<p>And if we set aside user Effects and external I/O and view it as a DOM state transformation, the rendering operation <code>u_s = h(render(s))</code> can be expressed as an idempotent endomorphism on the DOM:</p>
<p>$$u_{s}(u_{s}(\operatorname{dom})) = u_{s}(\operatorname{dom})$$</p>
<hr>
<p>$$\mathrm{UI} = f(\mathrm{State})$$</p>
<p>is useful as an educational intuition, but if we look at React&#39;s behavior more carefully, it needs some qualification.</p>
<p>A React Component is not a pure function in the mathematical sense.
The aspect interpreted by React Fiber can be expressed as an effectful computation in an algebraic effect system. At the same time, this expression does not explain the static Hook sequence first. The Rules of Hooks constrain the program shape first, and because of that constraint the program can be mapped fairly faithfully into an Applicative / Free Applicative-like structure that includes props as <code>useProps</code>.</p>
<p>$$\mathrm{UI} = \mathrm{ReactHandle}_{\mathrm{props}}(\mathrm{ComponentProgram})$$</p>
<p>With this understanding, React&#39;s &quot;rules,&quot; the behavior of Hooks, the mechanism of <code>use</code> and Suspense, and the design principles of Server Components become easier to reason about in two layers: <strong>interpretation of effects</strong> and <strong>a static Hook skeleton</strong>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>React is React, just. Part 2</title>
      <link>https://wtrclred.vercel.app/posts/08</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/08</guid>
      <description>Essay: Meta, Babel, Flow, and React Compiler</description>
      <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T09:22:06.000Z</dc:date>
      <category>react</category>
      <category>javascript</category>
      <category>oss</category>
      <category>meta</category>
      <category>babel</category>
      <category>flow</category>
      <category>compiler</category>
      <category>react-compiler</category>
      <category>language</category>
      <category>semantics</category>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Note</strong>: I am neither affiliated with Meta nor involved in React Compiler development. This article is purely based on observations from the outside.</p>
</blockquote>
<p>In <a href="https://wtrclred.vercel.app/posts/01">the previous article</a>, I discussed whether React Components are no longer &quot;Just JavaScript&quot; with the introduction of React Compiler.
This time, as a continuation, I&#39;ll take a broader look at Meta&#39;s ecosystem and explore more deeply how React is evolving as a &quot;language.&quot;</p>
<h1>Meta and JavaScript Infrastructure</h1>
<p>Meta (formerly Facebook) has made significant contributions to the JavaScript ecosystem over the years.</p>
<h2>Babel</h2>
<p><a href="https://babeljs.io/" target="_blank" rel="noopener noreferrer">Babel</a> is a JavaScript transpiler that originally started as 6to5.
It transforms ES6+ and JSX syntax that browsers don&#39;t natively support, maintaining backward compatibility.</p>
<blockquote>
<p>JSX was created as an extension to ECMAScript with the purpose of designing a more concise and easy-to-understand syntax for building DOM tree structures and attributes.
— <a href="https://medium.com/swlh/the-role-of-babel-in-react-dbcf78c69125" target="_blank" rel="noopener noreferrer">The Role of Babel in React</a></p>
</blockquote>
<p>Babel is an independent open-source project, but it&#39;s an essential part of the React ecosystem.
And <strong>React Compiler is also implemented as a Babel Plugin</strong>.</p>
<h2>Flow</h2>
<p><a href="https://flow.org/" target="_blank" rel="noopener noreferrer">Flow</a> is a static type checker for JavaScript developed by Meta.
<strong>Flow is also implemented as a Babel Plugin (<code>@babel/preset-flow</code>)</strong>, stripping type annotations and custom syntax at runtime.
And as one of today&#39;s main topics, <strong>Flow is extending beyond a type checker to become a &quot;language.&quot;</strong></p>
<h1>Flow&#39;s Component Syntax and Hook Syntax</h1>
<p>In April 2024, the Flow team announced <a href="https://flow.org/blog/2024/04/03/New-Flow-Language-Features-for-React/" target="_blank" rel="noopener noreferrer">Component Syntax</a>.
This provides <strong>first-class language support</strong> for React primitives like components and hooks.</p>
<h2>Why Was It Added?</h2>
<blockquote>
<p>Component Syntax was designed in coordination with the React team, and is already being used across Meta&#39;s codebases.
— <a href="https://flow.org/blog/2024/04/03/New-Flow-Language-Features-for-React/" target="_blank" rel="noopener noreferrer">New Flow Language Features for React</a></p>
</blockquote>
<blockquote>
<p>These features bring improved ergonomics, expressiveness, and <strong>static enforcement for many of the Rules of React</strong>.
— <a href="https://flow.org/blog/2024/04/03/New-Flow-Language-Features-for-React/" target="_blank" rel="noopener noreferrer">Flow Blog</a></p>
</blockquote>
<p>In other words, Flow&#39;s Component Syntax was <strong>designed in coordination with the React team</strong> and aims to <strong>statically enforce the Rules of React</strong>.</p>
<h2>Component Syntax</h2>
<p>Instead of regular function components, you define them using the <code>component</code> keyword:</p>
<pre><code class="language-js">// Traditional function component
function Introduction(props: { name: string, age: number }) {
  return (
    &lt;h1&gt;
      My name is {props.name} and I am {props.age} years old
    &lt;/h1&gt;
  );
}

// Flow&#39;s Component Syntax
component Introduction(name: string, age: number) {
  return (
    &lt;h1&gt;
      My name is {name} and I am {age} years old
    &lt;/h1&gt;
  );
}
</code></pre>
<p>Reference: <a href="https://flow.org/en/docs/react/component-syntax/" target="_blank" rel="noopener noreferrer">Component Syntax | Flow</a></p>
<p>With Component Syntax:</p>
<ul>
<li><p><strong>Props become direct parameters</strong>: Reduced boilerplate</p>
</li>
<li><p><strong>Automatic ref handling</strong>: Internally wrapped with <code>React.forwardRef</code></p>
</li>
<li><p><strong>Explicit return required</strong>: All branches must have a return</p>
</li>
<li><p><strong><code>this</code> is forbidden</strong>: Cannot use <code>this</code> inside components</p>
</li>
</ul>
<h2>Hook Syntax</h2>
<p>Similarly, you define hooks using the <code>hook</code> keyword:</p>
<pre><code class="language-js">// Traditional custom hook
function useOnlineStatus(initial: boolean): boolean {
  const [isOnline, setIsOnline] = useState(initial);
  // ...
  return isOnline;
}

// Flow&#39;s Hook Syntax
hook useOnlineStatus(initial: boolean): boolean {
  const [isOnline, setIsOnline] = useState(initial);
  // ...
  return isOnline;
}
</code></pre>
<p>Reference: <a href="https://flow.org/en/docs/react/hook-syntax/" target="_blank" rel="noopener noreferrer">Hook Syntax | Flow</a></p>
<h2>What Flow Detects</h2>
<p>Flow&#39;s Component/Hook Syntax <strong>statically</strong> detects code that violates the <a href="https://react.dev/reference/rules" target="_blank" rel="noopener noreferrer">Rules of React</a>:</p>
<ol>
<li><p><strong>Props mutation detection</strong>: Code that tries to mutate props inside a component</p>
</li>
<li><p><strong>Conditional hook call prevention</strong>: Reports hook calls inside if statements as errors</p>
</li>
<li><p><strong>Ref access during render detection</strong>: Code that reads or writes refs during render</p>
</li>
<li><p><strong>Nested component/hook detection</strong>: Defining another component/hook inside a component or hook</p>
</li>
</ol>
<blockquote>
<p>Flow is able to detect mutations of props in a component or hook.
Another pattern enforced in component bodies is that refs are not read or written to during render.
— <a href="https://flow.org/en/docs/react/component-syntax/" target="_blank" rel="noopener noreferrer">Flow Documentation</a></p>
</blockquote>
<h2>What This Means</h2>
<p>Flow&#39;s Component Syntax and Hook Syntax are <strong>an attempt to explicitly define React as a &quot;language.&quot;</strong></p>
<p>Previously, React components were defined as &quot;JavaScript functions.&quot;
However, by introducing new keywords (<code>component</code>, <code>hook</code>), Flow <strong>expresses React&#39;s semantics at the language level</strong>.</p>
<p>This more clearly demonstrates the conclusion from the previous article: &quot;React is React.&quot;</p>
<h1>React Compiler: The Implicit Approach</h1>
<p>While Flow &quot;explicitly&quot; introduces new syntax, React Compiler takes an &quot;implicit&quot; approach.</p>
<h2>History of React Compiler</h2>
<p>React Compiler is the result of about 10 years of research:</p>
<ul>
<li><p><strong>2017</strong>: Facebook first explored compilers with Prepack</p>
</li>
<li><p><strong>2021</strong>: Xuan Huang demoed the <a href="https://www.youtube.com/watch?v=lGEMwh32soc" target="_blank" rel="noopener noreferrer">first iteration</a> of the new approach</p>
</li>
<li><p><strong>2024</strong>: Beta release, announced at React Conf 2024</p>
</li>
<li><p><strong>October 2025</strong>: <a href="https://react.dev/blog/2025/10/07/react-compiler-1" target="_blank" rel="noopener noreferrer">React Compiler v1.0</a> stable release</p>
</li>
</ul>
<p>Reference: <a href="https://react.dev/blog/2025/10/07/react-compiler-1" target="_blank" rel="noopener noreferrer">React Compiler v1.0 – React</a></p>
<h2>Hooks: The Moment React Was Defined as a &quot;Language&quot;</h2>
<p>Here I want to present an important perspective.</p>
<p><strong>React was defined as a &quot;language&quot; at the moment Hooks were introduced.</strong></p>
<p>Hooks have <strong>Rules of Hooks</strong>, constraints that don&#39;t exist in JavaScript:</p>
<ul>
<li><p>Only call at the top level (not in loops, conditions, or nested functions)</p>
</li>
<li><p>Only call from React functions (not from regular JavaScript functions)</p>
</li>
</ul>
<p>These are rules that don&#39;t exist for JavaScript functions.
Functions that use Hooks are no longer &quot;JavaScript functions&quot; but <strong>&quot;React functions.&quot;</strong></p>
<p>In other words, <strong>the introduction of Hooks may have been the moment that defined React as a &quot;language&quot; with its own semantics</strong>.</p>
<h2>Hooks Design and the Compiler</h2>
<p>And Hooks were designed from the beginning with compiler optimization in mind.</p>
<p>The React Compiler v1.0 blog states:</p>
<blockquote>
<p>Hooks were designed with a future compiler in mind.
— <a href="https://react.dev/blog/2025/10/07/react-compiler-1" target="_blank" rel="noopener noreferrer">React Compiler v1.0</a></p>
</blockquote>
<p>This isn&#39;t an afterthought. <strong>The official Hooks documentation published in 2018</strong> already stated this intention:</p>
<blockquote>
<p>As Svelte, Angular, Glimmer, and others show, ahead-of-time compilation of components has a lot of future potential. Especially if it&#39;s not limited to templates.</p>
<p>— <a href="https://legacy.reactjs.org/docs/hooks-intro.html" target="_blank" rel="noopener noreferrer">Introducing Hooks – React (2018)</a></p>
</blockquote>
<blockquote>
<p>We wanted to present an API that makes it more likely for code to stay on the optimizable path. To solve these problems, Hooks let you use more of React&#39;s features without classes.</p>
<p>— <a href="https://legacy.reactjs.org/docs/hooks-intro.html" target="_blank" rel="noopener noreferrer">Introducing Hooks – React (2018)</a></p>
</blockquote>
<p>In other words, <strong>Hooks were designed from the beginning with compiler optimization in mind</strong>.</p>
<h2>Dan Abramov&#39;s Statements</h2>
<p>Dan Abramov, who was on the React team at the time, also mentioned automatic memoization by compilers early on.</p>
<p>In the article &quot;<a href="https://overreacted.io/before-you-memo/" target="_blank" rel="noopener noreferrer">Before You memo()</a>&quot; published in February 2021, after discussing the hassle of manual memoization, he stated:</p>
<blockquote>
<p>This last step is annoying, especially for components in between, and ideally a compiler would do it for you. In the future, it might.</p>
<p>— Dan Abramov, <a href="https://overreacted.io/before-you-memo/" target="_blank" rel="noopener noreferrer">Before You memo() (2021)</a></p>
</blockquote>
<p>&quot;In the future, a compiler might do this automatically&quot;—this statement was made more than 3 years before React Compiler was publicly released.</p>
<p>The React team had a clear vision of <strong>automatic optimization by compilers</strong> for many years.
This shows that React&#39;s evolution from &quot;Just JavaScript&quot; to &quot;a language with its own semantics&quot; was a planned process.</p>
<h2>What React Compiler Does</h2>
<p>As mentioned in the previous article, React Compiler automatically performs optimizations like this:</p>
<pre><code class="language-tsx">// Input
function VideoTag({ heading, videos, filter }) {
  const filteredVideos = [];
  for (const video of videos) {
    if (applyFilter(video, filter)) {
      filteredVideos.push(video);
    }
  }
  // ...
}
</code></pre>
<pre><code class="language-tsx">// Output (after compilation)
function VideoTab(t36) {
  const $ = useMemoCache(12);
  const { heading, videos, filter } = t36;
  let filteredVideos;

  if ($[0] !== videos || $[1] !== filter) {
    filteredVideos = [];
    for (const video of videos) {
      if (applyFilter(video, filter)) {
        filteredVideos.push(video);
      }
    }
    $[0] = videos;
    $[1] = filter;
    $[2] = filteredVideos;
  } else {
    filteredVideos = $[2];
  }
  // ...
}
</code></pre>
<p>Reference: <a href="https://youtu.be/qOQClO3g8-Y?t=497" target="_blank" rel="noopener noreferrer">Understanding Idiomatic React – Joe Savona, Mofei Zhang, React Advanced 2023</a></p>
<h2>Conditional Memoization: What useMemo Can&#39;t Do</h2>
<p>The React Compiler v1.0 blog shows examples of <strong>optimizations that manual <code>useMemo</code>/<code>useCallback</code> cannot achieve</strong>:</p>
<pre><code class="language-js">export default function ThemeProvider(props) {
  if (!props.children) {
    return null;
  }
  // The compiler can memoize code after conditional returns
  // (impossible with manual useMemo/useCallback)
  const theme = mergeTheme(props.theme, use(ThemeContext));
  return &lt;ThemeContext value={theme}&gt;{props.children}&lt;/ThemeContext&gt;;
}
</code></pre>
<blockquote>
<p>This is a compiler-specific capability that cannot be implemented with <code>useMemo</code>/<code>useCallback</code>.
— <a href="https://react.dev/blog/2025/10/07/react-compiler-1" target="_blank" rel="noopener noreferrer">React Compiler v1.0</a></p>
</blockquote>
<h2>Static Enforcement of Rules of React</h2>
<p>React Compiler includes <strong>a pass that validates the Rules of React</strong>, and diagnostics are provided through the ESLint plugin:</p>
<blockquote>
<p>React Compiler&#39;s ESLint plugin helps developers proactively identify and correct Rules of React violations. The team strongly recommends everyone use the linter today.
— <a href="https://react.dev/blog/2024/10/21/react-compiler-beta-release" target="_blank" rel="noopener noreferrer">React Compiler Beta Release</a></p>
</blockquote>
<h2>Results at Meta</h2>
<p>React Compiler is already running in production at Meta:</p>
<ul>
<li><p><strong>Instagram</strong>: Average 3% improvement across all pages</p>
</li>
<li><p><strong>Quest Store</strong>: At least 4% improvement in load times, some interactions more than 2x faster, initial loads improved by up to 12%</p>
</li>
</ul>
<p>Reference: <a href="https://react.dev/blog/2025/10/07/react-compiler-1" target="_blank" rel="noopener noreferrer">React Compiler v1.0</a></p>
<h1>Integration Between React Compiler and Flow</h1>
<p>What&#39;s important here is that <strong>React Compiler explicitly supports Flow&#39;s Component/Hook Syntax</strong>.</p>
<h2>compilationMode Option</h2>
<p>React Compiler has a <a href="https://react.dev/reference/react-compiler/compilationMode" target="_blank" rel="noopener noreferrer"><code>compilationMode</code></a> option that controls which functions are compiled:</p>
<ul>
<li><p><strong><code>&#39;infer&#39;</code></strong>　: Uses component naming conventions (PascalCase) and hook detection (<code>use</code> prefix)</p>
</li>
<li><p><strong><code>&#39;annotation&#39;</code></strong>: Only functions marked with <code>&quot;use memo&quot;</code> directive</p>
</li>
<li><p><strong><code>&#39;syntax&#39;</code></strong>: <strong>Only functions using Flow&#39;s component/hook syntax</strong></p>
</li>
<li><p><strong><code>&#39;all&#39;</code></strong>: All top-level functions (not recommended)</p>
</li>
</ul>
<h2><code>&#39;syntax&#39;</code> Mode</h2>
<p>When using <code>&#39;syntax&#39;</code> mode, only components and hooks defined with Flow&#39;s dedicated syntax are compiled:</p>
<pre><code class="language-js">// babel.config.js
{
  compilationMode: &quot;syntax&quot;;
}
</code></pre>
<pre><code class="language-js">// ✅ Compiled: Flow component syntax
component Button(label: string) {
  return &lt;button&gt;{label}&lt;/button&gt;;
}

// ✅ Compiled: Flow hook syntax
hook useCounter(initial: number) {
  const [count, setCount] = useState(initial);
  return [count, setCount];
}

// ❌ Not compiled: Regular function syntax
function helper(data) {
  return process(data);
}
</code></pre>
<p>Reference: <a href="https://react.dev/reference/react-compiler/compilationMode" target="_blank" rel="noopener noreferrer">compilationMode – React</a></p>
<h2>What This Means</h2>
<p>Flow and React Compiler are <strong>not separate projects, but designed to work together</strong>.</p>
<ul>
<li><p>Flow&#39;s Component Syntax was designed in coordination with the React team</p>
</li>
<li><p>React Compiler explicitly supports Flow&#39;s syntax</p>
</li>
<li><p>Inside Meta, these two are used in combination</p>
</li>
</ul>
<p>In other words, Meta is developing both Flow and React Compiler under a consistent strategy of &quot;defining React as a language.&quot;</p>
<h1>React Documentation and the function Keyword</h1>
<p>Here I want to share an observation.</p>
<p>In <a href="https://react.dev/" target="_blank" rel="noopener noreferrer">React&#39;s official documentation</a>, the <strong><code>function</code> keyword is consistently used</strong> for component definitions:</p>
<pre><code class="language-js">export default function Profile() {
  return &lt;img src=&quot;https://i.imgur.com/MK3eW3Am.jpg&quot; alt=&quot;Katherine Johnson&quot; /&gt;;
}
</code></pre>
<p>Reference: <a href="https://react.dev/learn/your-first-component" target="_blank" rel="noopener noreferrer">Your First Component – React</a></p>
<p>There is <strong>no mention whatsoever</strong> of arrow functions (<code>const Profile = () =&gt; {}</code>).</p>
<h2>Why Not Arrow Functions?</h2>
<p>Dan Abramov stated on <a href="https://overreacted.io/how-are-function-components-different-from-classes/" target="_blank" rel="noopener noreferrer">overreacted.io</a>:</p>
<blockquote>
<p>it doesn&#39;t matter whether I use arrows or function declarations
— Dan Abramov</p>
</blockquote>
<p>Technically, both arrow functions and function declarations work fine as React components.
However, there are some considerations as to why the React documentation continues to use the <code>function</code> keyword.</p>
<h3>1. Affinity with Flow&#39;s Component Syntax</h3>
<p>Flow&#39;s Component Syntax uses the <code>component</code> keyword:</p>
<pre><code class="language-js">component Introduction(name: string, age: number) {
  // ...
}
</code></pre>
<p>This can be naturally introduced as a replacement for the <code>function</code> keyword.
If the React documentation had recommended arrow functions, this transition would be unnatural.</p>
<h3>2. Syntactic Consistency</h3>
<p>Function declarations with the <code>function</code> keyword can be naturally replaced when transitioning to dedicated syntax (like Flow&#39;s Component Syntax) in the future.
The transition from function declarations is more syntactically consistent than from arrow functions.</p>
<h1>Comparing the Two Approaches</h1>
<p>Flow and React Compiler take different approaches to the same problem:</p>
<p><strong>Flow</strong>:</p>
<ul>
<li><p>Method: New keywords (<code>component</code>, <code>hook</code>)</p>
</li>
<li><p>Explicitness: Explicit (new syntax)</p>
</li>
<li><p>Target: Meta&#39;s internal codebases</p>
</li>
<li><p>Purpose: Static enforcement of Rules of React</p>
</li>
</ul>
<p><strong>React Compiler</strong>:</p>
<ul>
<li><p>Method: Transforms existing syntax</p>
</li>
<li><p>Explicitness: Implicit (transformation)</p>
</li>
<li><p>Target: All React users</p>
</li>
<li><p>Purpose: Auto-memoization + Rules of React validation</p>
</li>
</ul>
<p>However, <strong>both are aligned in the direction that &quot;React has its own semantics.&quot;</strong></p>
<h1>Rules of React</h1>
<p>Let&#39;s review the <a href="https://react.dev/reference/rules" target="_blank" rel="noopener noreferrer">Rules of React</a>:</p>
<blockquote>
<p>Rules are guidelines for writing idiomatic, high-quality React code. They&#39;re not just guidelines—if you don&#39;t follow them, you&#39;ll have bugs.
— <a href="https://react.dev/reference/rules" target="_blank" rel="noopener noreferrer">Rules of React – React</a></p>
</blockquote>
<p>Key rules:</p>
<ol>
<li><p><strong>Components and Hooks must be Pure</strong></p>
<ul>
<li><p>Idempotent: Same output for same input</p>
</li>
<li><p>Side effects only outside of rendering</p>
</li>
<li><p>props, state, Hook return values are immutable</p>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>React calls Components and Hooks</strong></p>
<ul>
<li><p>Don&#39;t call component functions directly</p>
</li>
<li><p>Don&#39;t pass Hooks as regular values</p>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p><strong>Rules of Hooks</strong></p>
<ul>
<li><p>Only call at the top level</p>
</li>
<li><p>Only call from React functions</p>
</li>
</ul>
</li>
</ol>
<p>These rules form the &quot;React semantics.&quot;
And both Flow&#39;s Component Syntax and React Compiler aim to <strong>statically enforce</strong> these rules.</p>
<h1>Conclusion: React is Evolving as a &quot;Language&quot;</h1>
<p>In the previous article, I concluded:</p>
<blockquote>
<p>React is React.
It&#39;s a separate entity with the same syntax as JavaScript but different semantics.</p>
</blockquote>
<p>This investigation has reinforced that conclusion:</p>
<ol>
<li><p><strong>Flow&#39;s Component Syntax</strong> <strong>explicitly defines React semantics as a language</strong></p>
</li>
<li><p><strong>React Compiler</strong> <strong>implicitly transforms React semantics into JavaScript</strong></p>
</li>
<li><p><strong>Hooks were designed from the beginning with compilers in mind</strong></p>
</li>
<li><p><strong>React documentation consistently uses the <code>function</code> keyword</strong>, possibly preparing for future syntax extensions</p>
</li>
</ol>
<p>The era when React was &quot;Just JavaScript&quot; is gradually coming to an end.
<strong>React is React</strong>—the meaning of these words will become increasingly important.</p>
<hr>
<p>This is merely my essay and does not represent the official views of the React Team or Meta.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Characterize Vue.js</title>
      <link>https://wtrclred.vercel.app/posts/07</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/07</guid>
      <description>What makes Vue.js, Vue.js</description>
      <pubDate>Thu, 25 Dec 2025 00:00:00 GMT</pubDate>
      <dc:date>2026-04-24T16:40:55.000Z</dc:date>
      <category>vue</category>
      <category>javascript</category>
      <category>oss</category>
      <category>vite</category>
      <category>voidzero</category>
      <category>ecosystem</category>
      <category>community</category>
      <category>language</category>
      <content:encoded><![CDATA[<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 24 24&quot; style=&quot;max-width: 180px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;vueGradient&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;100%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;&gt;
        &lt;animate attributeName=&quot;stop-color&quot; values=&quot;#42b883;#35495e;#42b883&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;/stop&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;&gt;
        &lt;animate attributeName=&quot;stop-color&quot; values=&quot;#35495e;#42b883;#35495e&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;/stop&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;path fill=&quot;url(#vueGradient)&quot; d=&quot;M2 3h3.5L12 15l6.5-12H22L12 21zm4.5 0h3L12 7.58L14.5 3h3L12 13.08z&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/path&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Preface</h1>
<p>What does Vue.js mean to you?</p>
<p>Whether you write Vue.js for work every day, explore it as a hobby, or simply know the name through social media — there are all kinds of perspectives.</p>
<p>Too much to learn?<br>
Lack of rules makes it confusing?<br>
Easy?<br>
Migration is painful?<br>
But somehow you like it?<br>
Or you still don&#39;t?</p>
<p>Everyone holds different feelings and impressions.</p>
<p>Today, I want to take those intuitions you&#39;ve felt about Vue.js and organize them systematically. That&#39;s what this essay attempts to do.</p>
<p>Let me characterize Vue.js through <strong>4 distinct characters</strong>.</p>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 100&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;titleGrad1&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;0%;100%;0%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;100%;200%;100%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot; font-size=&quot;32&quot; font-weight=&quot;bold&quot; fill=&quot;url(#titleGrad1)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    1 / 4 — Approachable
  &lt;/text&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;85&quot; x2=&quot;500&quot; y2=&quot;85&quot; stroke=&quot;url(#titleGrad1)&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;5,5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dashoffset&quot; values=&quot;0;-20&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Vue.js is Easy?</h1>
<p>Have you heard this phrase before?</p>
<p><strong>&quot;Vue.js is Easy&quot;</strong></p>
<p>You often see the Easy/Simple comparison in discussions.</p>
<p>Honestly, I&#39;m not particularly fond of this phrase.<br>
First, what feels &quot;easy&quot; is highly subjective and depends heavily on one&#39;s background.<br>
I&#39;m already hesitant to casually use the word &quot;easy,&quot; but this phrase can create <strong>two misconceptions</strong>.</p>
<h2>Misconception 1: &quot;Vue.js has become difficult&quot;</h2>
<p>Vue.js announced version 3 with the Composition API in 2020, bringing significant changes.<br>
And since then, it has continued to evolve.</p>
<p>During this time, some have said &quot;Vue.js has become difficult&quot; or &quot;the old Vue.js was simpler and better.&quot;</p>
<p>Indeed, there was a time when Vue.js aimed to be simple and approachable — more &quot;Easy&quot; than it is today.</p>
<p>But here&#39;s what I want you to consider: <strong>&quot;Was it really Vue.js that became difficult?&quot;</strong></p>
<p>I don&#39;t think so.</p>
<p><strong>What became difficult was the Web.</strong></p>
<p>Since the 2010s when frontend development emerged, the complexity of UIs, the scale of frontend codebases, and performance challenges have all grown. Web applications as a whole became more difficult.<br>
Vue.js simply followed those needs.</p>
<p>Evidence for this: <strong>&quot;Vue.js still supports traditional patterns.&quot;</strong><br>
Specifically: not using TypeScript, using Vue.js from a CDN, Options API — these legacy patterns are still supported today.</p>
<p>In other words, you can still use &quot;the Vue that wasn&#39;t difficult.&quot;</p>
<h2>Misconception 2: &quot;Vue.js has no rules, so it&#39;s bad&quot;</h2>
<p>This is the misconception that &quot;Vue.js is easy but has no rules. That breeds bugs and makes code messy.&quot;</p>
<p>Establishing design patterns and conventions in programs is generally considered good practice. I agree.<br>
However, there are important considerations when establishing rules.</p>
<p>First: <strong>&quot;What problem does this rule solve?&quot;</strong><br>
Rules need purpose. You should always consider what benefits a rule provides.</p>
<p>Second: <strong>The tradeoffs it creates.</strong><br>
Conventions aren&#39;t free. The stronger the constraints, the more compliance is expected, reducing flexibility and increasing learning costs.</p>
<p>Does this mean I&#39;m saying rules are bad? No.<br>
<strong>&quot;Choose what fits you based on tradeoffs.&quot;</strong></p>
<p>Web applications are incredibly diverse. The teams and companies building them are equally diverse.<br>
There&#39;s no silver bullet that works for everything.</p>
<p>Moreover, design and conventions aren&#39;t binary.<br>
I&#39;m not saying &quot;rules aren&#39;t needed.&quot; I&#39;m saying <strong>&quot;rules are needed, but there are many options. The ability to choose has great value.&quot;</strong></p>
<p>Whether the framework or the implementer takes responsibility is a tradeoff.<br>
When implementers take that responsibility, they trade some accountability for flexibility in conventions.</p>
<p>The response &quot;Vue.js is easy but has no rules, so it&#39;s bad&quot; fails to adequately consider this tradeoff.</p>
<h2>Vue.js is Approachable</h2>
<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg viewBox=&quot;0 0 400 120&quot; style=&quot;max-width: 400px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;approachGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;rect x=&quot;20&quot; y=&quot;20&quot; width=&quot;360&quot; height=&quot;80&quot; rx=&quot;10&quot; fill=&quot;none&quot; stroke=&quot;url(#approachGrad)&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,1000;880,0;0,1000&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/rect&gt;
  &lt;text x=&quot;200&quot; y=&quot;70&quot; text-anchor=&quot;middle&quot; font-size=&quot;24&quot; font-weight=&quot;bold&quot; fill=&quot;url(#approachGrad)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    Approachable
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0;1;1;0&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<p>So what is Vue.js, really?<br>
I believe <strong>Vue.js is Approachable</strong>.</p>
<p>The word &quot;Approachable&quot; actually appears at the top of the official documentation.</p>
<p>https://vuejs.org/</p>
<p>Despite various tradeoffs, the consistent point is <strong>&quot;easy to approach for various use cases.&quot;</strong></p>
<p>Vue.js is also called <strong>&quot;The Progressive Framework.&quot;</strong><br>
This is a related concept to Approachable — meaning &quot;a framework that can grow with you and adapt to your needs.&quot;<br>
You can start with enhancing static HTML without a build step, then scale up to SPA, SSR, or SSG as needed — or start with a full-stack setup from the beginning.<br>
Approachable at any scale, from any starting point — this is another expression of Vue.js&#39;s flexibility.</p>
<p>https://vuejs.org/guide/introduction#the-progressive-framework</p>
<p>Vue.js is an Approachable UI framework.</p>
<p>It hasn&#39;t become difficult, nor does it sacrifice stability for simplicity.<br>
It&#39;s about <strong>flexible choices</strong>.</p>
<blockquote>
<p>Vue.js followed the Web as it became more complex. Traditional patterns are still available.<br>
Implementers can flexibly choose conventions and design.</p>
</blockquote>
<p>This is the <strong>first character</strong> of Vue.js I want to discuss today.</p>
<blockquote>
<p>I&#39;ve written a deeper dive on this topic. Check it out if you&#39;re interested.</p>
<p><a href="https://wtrclred.vercel.app/posts/06">Vue.js is not Easy. It is Approachable.</a></p>
</blockquote>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 100&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;titleGrad2&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;0%;100%;0%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;100%;200%;100%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot; font-size=&quot;32&quot; font-weight=&quot;bold&quot; fill=&quot;url(#titleGrad2)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    2 / 4 — Language
  &lt;/text&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;85&quot; x2=&quot;500&quot; y2=&quot;85&quot; stroke=&quot;url(#titleGrad2)&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;5,5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dashoffset&quot; values=&quot;0;-20&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Vue.js is a Language</h1>
<p>Before explaining how Vue.js is a language, let&#39;s review what programming languages are.</p>
<h2>What is a Programming Language?</h2>
<p>What is a programming language?<br>
It&#39;s a language for writing programs. Just as the name suggests.</p>
<p>You know that many programming languages exist in this world.<br>
JavaScript, TypeScript, Rust, C++, Haskell.<br>
And not just those. HTML, CSS, and JSON are also part of this family.</p>
<p>These are classified as &quot;<strong>general-purpose programming languages</strong>&quot; or &quot;<strong>domain-specific languages (DSL)</strong>.&quot; Both are languages.</p>
<p>Why do so many languages exist?</p>
<p>Think about this: what format does a computer actually need to store programs?</p>
<p>The answer is <strong>binary</strong>. With just binary, everything can be described.<br>
In most situations, any language ultimately becomes binary to execute.</p>
<p>So who are programming languages for?<br>
Undoubtedly, they&#39;re for us — <strong>humans</strong>.</p>
<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg viewBox=&quot;0 0 620 300&quot; style=&quot;max-width: 620px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;marker id=&quot;languageArrow&quot; markerWidth=&quot;10&quot; markerHeight=&quot;7&quot; refX=&quot;9&quot; refY=&quot;3.5&quot; orient=&quot;auto&quot;&gt;
      &lt;polygon points=&quot;0 0, 10 3.5, 0 7&quot; fill=&quot;#35495e&quot;/&gt;
    &lt;/marker&gt;
  &lt;/defs&gt;</p>
<p>&lt;text x=&quot;135&quot; y=&quot;32&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#35495e&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Abstraction for humans&lt;/text&gt;</p>
<p>&lt;rect x=&quot;40&quot; y=&quot;214&quot; width=&quot;190&quot; height=&quot;56&quot; rx=&quot;8&quot; fill=&quot;#35495e&quot; fill-opacity=&quot;0.06&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot;/&gt;
  &lt;text x=&quot;135&quot; y=&quot;237&quot; text-anchor=&quot;middle&quot; font-size=&quot;16&quot; fill=&quot;#35495e&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Binary&lt;/text&gt;
  &lt;text x=&quot;135&quot; y=&quot;257&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#666&quot; font-family=&quot;monospace&quot;&gt;01010110...&lt;/text&gt;</p>
<p>&lt;rect x=&quot;40&quot; y=&quot;136&quot; width=&quot;190&quot; height=&quot;56&quot; rx=&quot;8&quot; fill=&quot;#35495e&quot; fill-opacity=&quot;0.06&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot;/&gt;
  &lt;text x=&quot;135&quot; y=&quot;159&quot; text-anchor=&quot;middle&quot; font-size=&quot;16&quot; fill=&quot;#35495e&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Assembly&lt;/text&gt;
  &lt;text x=&quot;135&quot; y=&quot;179&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#666&quot; font-family=&quot;monospace&quot;&gt;mov, jmp...&lt;/text&gt;</p>
<p>&lt;path d=&quot;M135 210 L135 198 M128 205 L135 198 L142 205&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2.3&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;/&gt;
  &lt;path d=&quot;M135 132 L135 120 M128 127 L135 120 L142 127&quot; fill=&quot;none&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2.3&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;/&gt;</p>
<p>&lt;rect x=&quot;40&quot; y=&quot;58&quot; width=&quot;190&quot; height=&quot;56&quot; rx=&quot;8&quot; fill=&quot;#42b883&quot; fill-opacity=&quot;0.14&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;2.5&quot;/&gt;
  &lt;text x=&quot;135&quot; y=&quot;81&quot; text-anchor=&quot;middle&quot; font-size=&quot;16&quot; fill=&quot;#35495e&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;High-level language&lt;/text&gt;
  &lt;text x=&quot;135&quot; y=&quot;101&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#35495e&quot; font-family=&quot;system-ui&quot;&gt;C, JS, Rust...&lt;/text&gt;</p>
<p>&lt;line x1=&quot;246&quot; y1=&quot;86&quot; x2=&quot;318&quot; y2=&quot;86&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;3&quot; marker-end=&quot;url(#languageArrow)&quot;/&gt;</p>
<p>&lt;text x=&quot;450&quot; y=&quot;82&quot; text-anchor=&quot;middle&quot; font-size=&quot;24&quot; fill=&quot;#35495e&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vue.js — DSL for UI&lt;/text&gt;
&lt;line x1=&quot;320&quot; y1=&quot;102&quot; x2=&quot;580&quot; y2=&quot;102&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;2&quot;/&gt;</p>
<p>&lt;circle cx=&quot;337&quot; cy=&quot;138&quot; r=&quot;15&quot; fill=&quot;#42b883&quot;/&gt;
  &lt;text x=&quot;337&quot; y=&quot;143&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;1&lt;/text&gt;
  &lt;text x=&quot;365&quot; y=&quot;143&quot; font-size=&quot;15&quot; fill=&quot;#35495e&quot; font-family=&quot;system-ui&quot;&gt;Extends HTML + CSS + JS&lt;/text&gt;</p>
<p>&lt;circle cx=&quot;337&quot; cy=&quot;180&quot; r=&quot;15&quot; fill=&quot;#42b883&quot;/&gt;
  &lt;text x=&quot;337&quot; y=&quot;185&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;2&lt;/text&gt;
  &lt;text x=&quot;365&quot; y=&quot;185&quot; font-size=&quot;15&quot; fill=&quot;#35495e&quot; font-family=&quot;system-ui&quot;&gt;Declarative UI description&lt;/text&gt;</p>
<p>&lt;circle cx=&quot;337&quot; cy=&quot;222&quot; r=&quot;15&quot; fill=&quot;#42b883&quot;/&gt;
  &lt;text x=&quot;337&quot; y=&quot;227&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;3&lt;/text&gt;
  &lt;text x=&quot;365&quot; y=&quot;227&quot; font-size=&quot;15&quot; fill=&quot;#35495e&quot; font-family=&quot;system-ui&quot;&gt;Component-oriented&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<p>There are diverse things to describe in the world.<br>
Hardware control, operating systems, browsers, web applications, CLI tools.<br>
Each domain requires different knowledge.</p>
<p>Many languages exist to provide &quot;writability&quot; and &quot;correctness&quot; for humans based on various concepts in each domain.</p>
<p>Starting from unstructured programming, we created syntax to give control knowledge to programming languages themselves, created OOP languages for OOP, FP languages for FP, new languages to escape memory management, and data description languages to describe data.</p>
<h2>Vue.js is a Language for Describing UI</h2>
<p>So what about Vue.js?<br>
Vue.js can be understood as a <strong>language for describing UI</strong>.</p>
<p>Languages for describing UI already exist: HTML, CSS, and JavaScript.<br>
Vue.js extends these with missing knowledge.</p>
<h3>1. Declarative Template Description</h3>
<p>Describing UI templates declaratively and dynamically.<br>
This practice wasn&#39;t common when HTML, CSS, and JavaScript first appeared.</p>
<p>As the Web evolved, the complexity of client-side code grew, various UI description methods emerged, and we learned that declarative UI description is better for stability and readability.</p>
<p>Vue.js supports a language for describing templates and solved this problem.</p>
<h3>2. Component-Centered Design</h3>
<p>Bundling related HTML, CSS, and JavaScript together to increase cohesion of UI-related code.<br>
Additionally, scoped CSS and slots are part of the extended knowledge Vue.js provides.</p>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref } from &quot;vue&quot;;
const count = ref(0);
function increment() {
  count.value++;
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button type=&quot;button&quot; @click=&quot;increment&quot;&gt;count is: {{ count }}&lt;/button&gt;
&lt;/template&gt;

&lt;style scoped&gt;
button {
  color: #42b883;
}
&lt;/style&gt;
</code></pre>
<p>What&#39;s notable here is that in the <code>&lt;script&gt;</code> section, you can <strong>use the JavaScript mental model almost as-is</strong>.<br>
As long as you&#39;re mindful of the boundary between View and ViewModel — that is, Reactivity — the rest is just plain JavaScript.<br>
There are almost no special rules or framework-specific constraints. This is another aspect of Vue.js&#39;s Approachability.</p>
<h3>3. Compiler as Optimizer</h3>
<p>Performance tuning that can be statically analyzed by a compiler shouldn&#39;t be done by hand.<br>
Such optimization should be left to the compiler while we focus on the essential task of describing UI.</p>
<p>Vue.js&#39;s compiler performs various optimizations:</p>
<ul>
<li><p><strong>Static Hoisting</strong>: Hoists static nodes to avoid regeneration</p>
</li>
<li><p><strong>Patch Flags</strong>: Adds flags to dynamic parts for faster diff detection</p>
</li>
<li><p><strong>Tree Flattening</strong>: Flattens nested static nodes</p>
</li>
<li><p><strong>Vapor Mode</strong>: More direct rendering without virtual DOM</p>
</li>
</ul>
<h2>Syntax as Language Features</h2>
<p>You know if statements, while, for, loop, and so on.<br>
But from the computer&#39;s perspective, these are essentially just &quot;check and jump.&quot;<br>
Why do multiple syntaxes exist for this single &quot;check and jump&quot; instruction?</p>
<p>Because it&#39;s easier for humans when each use case has its own meaning.<br>
When you simply want to branch: &quot;if.&quot; When you want to repeat while a condition holds: &quot;while.&quot;<br>
Even though it&#39;s just a jump for the computer, such meaning helps humans write programs.</p>
<p>The same applies to Vue.js.<br>
Programmatically, passing data between components is just &quot;passing.&quot;<br>
But when describing UI, we can extend UI knowledge and add meaning:</p>
<ul>
<li><p>When you want to bind data to a component: <code>v-bind</code></p>
</li>
<li><p>When you want to subscribe/emit events: <code>v-on</code>, <code>emit</code></p>
</li>
<li><p>When you want to inject templates into components: <code>slot</code></p>
</li>
</ul>
<p>Yes, <strong>Vue.js organized the knowledge that arises when describing UI as a language.</strong><br>
This is very much the philosophy of programming languages.</p>
<h2>Language Tools</h2>
<p>One more thing not to forget: <strong>&quot;Vue.js has well-developed language tools.&quot;</strong></p>
<p>Creating a programming language involves many challenges.<br>
Specification design, compiler implementation, and language tools.<br>
It takes enormous time and effort.</p>
<p>Indeed, Vue.js was somewhat behind other frameworks in the past.<br>
But time has passed, and with tremendous effort, things have improved significantly.</p>
<p>I want to express gratitude again to <a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener noreferrer">vuejs/language-tools</a>, <a href="https://github.com/johnsoncodehk" target="_blank" rel="noopener noreferrer">Johnson Chu</a>, and all community members.</p>
<blockquote>
<p>I&#39;ve written a deeper dive on &quot;Vue.js is a language&quot; as well.</p>
<p><a href="https://wtrclred.vercel.app/posts/05">What is Vue.js? It&#39;s just a language lol</a></p>
</blockquote>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 100&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;titleGrad3&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;0%;100%;0%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;100%;200%;100%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot; font-size=&quot;32&quot; font-weight=&quot;bold&quot; fill=&quot;url(#titleGrad3)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    3 / 4 — Ecosystem
  &lt;/text&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;85&quot; x2=&quot;500&quot; y2=&quot;85&quot; stroke=&quot;url(#titleGrad3)&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;5,5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dashoffset&quot; values=&quot;0;-20&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Vue.js: An Expanding Ecosystem</h1>
<p>Another major characteristic of Vue.js is the <strong>ecosystem that starts from Vue.js and expands beyond its boundaries.</strong></p>
<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg viewBox=&quot;-20 0 740 500&quot; style=&quot;max-width: 740px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;evanGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;100%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#646cff&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;</p>
<p>&lt;!-- Central Evan You node --&gt;
  &lt;circle cx=&quot;360&quot; cy=&quot;70&quot; r=&quot;50&quot; fill=&quot;url(#evanGrad)&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;50;53;50&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;360&quot; y=&quot;66&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Evan You&lt;/text&gt;
  &lt;text x=&quot;360&quot; y=&quot;80&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#ddd&quot; font-family=&quot;system-ui&quot;&gt;Creator&lt;/text&gt;</p>
<p>&lt;!-- Evan You to Vue.js connection --&gt;
  &lt;line x1=&quot;310&quot; y1=&quot;70&quot; x2=&quot;235&quot; y2=&quot;70&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;3&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0;0,100&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;185&quot; cy=&quot;70&quot; r=&quot;45&quot; fill=&quot;#42b883&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;45;48;45&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;185&quot; y=&quot;75&quot; text-anchor=&quot;middle&quot; font-size=&quot;15&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vue.js&lt;/text&gt;</p>
<p>&lt;!-- Evan to Vite connection (Evan created Vite) --&gt;
  &lt;line x1=&quot;325&quot; y1=&quot;110&quot; x2=&quot;260&quot; y2=&quot;160&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;2&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0;0,100&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Vue.js to Vite connection (Vue birthed Vite) --&gt;
  &lt;line x1=&quot;210&quot; y1=&quot;110&quot; x2=&quot;230&quot; y2=&quot;155&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;3&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,60;60,0;0,60&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;250&quot; cy=&quot;195&quot; r=&quot;40&quot; fill=&quot;#646cff&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;40;43;40&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;250&quot; y=&quot;200&quot; text-anchor=&quot;middle&quot; font-size=&quot;13&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vite&lt;/text&gt;</p>
<p>&lt;!-- VoidZero --&gt;
  &lt;line x1=&quot;360&quot; y1=&quot;120&quot; x2=&quot;360&quot; y2=&quot;160&quot; stroke=&quot;#f59e0b&quot; stroke-width=&quot;3&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,50;50,0;0,50&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;360&quot; cy=&quot;200&quot; r=&quot;40&quot; fill=&quot;#f59e0b&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;40;43;40&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;360&quot; y=&quot;205&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;VoidZero&lt;/text&gt;</p>
<p>&lt;!-- VoidZero to Vite connection --&gt;
  &lt;line x1=&quot;320&quot; y1=&quot;200&quot; x2=&quot;290&quot; y2=&quot;195&quot; stroke=&quot;#f59e0b&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,50;50,0;0,50&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Volar.js from Vue --&gt;
  &lt;line x1=&quot;140&quot; y1=&quot;70&quot; x2=&quot;95&quot; y2=&quot;70&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,50;50,0;0,50&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;65&quot; cy=&quot;70&quot; r=&quot;25&quot; fill=&quot;#35495e&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;65&quot; y=&quot;75&quot; text-anchor=&quot;middle&quot; font-size=&quot;8&quot; fill=&quot;#42b883&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Volar.js&lt;/text&gt;</p>
<p>&lt;!-- Volar.js expansions --&gt;
  &lt;line x1=&quot;50&quot; y1=&quot;92&quot; x2=&quot;35&quot; y2=&quot;125&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,40;40,0;0,40&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;30&quot; cy=&quot;145&quot; r=&quot;16&quot; fill=&quot;#ff5d01&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.7;0.9;0.7&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;30&quot; y=&quot;149&quot; text-anchor=&quot;middle&quot; font-size=&quot;6&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Astro&lt;/text&gt;</p>
<p>&lt;line x1=&quot;80&quot; y1=&quot;92&quot; x2=&quot;95&quot; y2=&quot;125&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,40;40,0;0,40&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;100&quot; cy=&quot;145&quot; r=&quot;16&quot; fill=&quot;#fcb32c&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.7;0.9;0.7&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;100&quot; y=&quot;149&quot; text-anchor=&quot;middle&quot; font-size=&quot;6&quot; fill=&quot;#000&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;MDX&lt;/text&gt;</p>
<p>&lt;!-- Nuxt from Vue (curved path to avoid Vite) --&gt;
  &lt;line x1=&quot;150&quot; y1=&quot;100&quot; x2=&quot;80&quot; y2=&quot;270&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,200;200,0;0,200&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;65&quot; cy=&quot;300&quot; r=&quot;25&quot; fill=&quot;#00dc82&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;65&quot; y=&quot;305&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Nuxt&lt;/text&gt;</p>
<p>&lt;!-- UnJS from Nuxt --&gt;
  &lt;line x1=&quot;65&quot; y1=&quot;325&quot; x2=&quot;65&quot; y2=&quot;355&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,40;40,0;0,40&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;65&quot; cy=&quot;380&quot; r=&quot;22&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.7;0.9;0.7&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;65&quot; y=&quot;380&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;UnJS&lt;/text&gt;</p>
<p>&lt;!-- Nitro from UnJS --&gt;
  &lt;line x1=&quot;87&quot; y1=&quot;375&quot; x2=&quot;115&quot; y2=&quot;355&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,40;40,0;0,40&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;135&quot; cy=&quot;340&quot; r=&quot;16&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;135&quot; y=&quot;340&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;6&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Nitro&lt;/text&gt;</p>
<p>&lt;!-- h3 from Nitro --&gt;
  &lt;line x1=&quot;151&quot; y1=&quot;340&quot; x2=&quot;180&quot; y2=&quot;340&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,30;30,0;0,30&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;198&quot; cy=&quot;340&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;198&quot; y=&quot;340&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;h3&lt;/text&gt;</p>
<p>&lt;!-- citty from UnJS --&gt;
  &lt;line x1=&quot;87&quot; y1=&quot;380&quot; x2=&quot;120&quot; y2=&quot;380&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,35;35,0;0,35&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;138&quot; cy=&quot;380&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;138&quot; y=&quot;380&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;citty&lt;/text&gt;</p>
<p>&lt;!-- ofetch from UnJS --&gt;
  &lt;line x1=&quot;87&quot; y1=&quot;388&quot; x2=&quot;115&quot; y2=&quot;410&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,35;35,0;0,35&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;135&quot; cy=&quot;425&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;135&quot; y=&quot;425&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;ofetch&lt;/text&gt;</p>
<p>&lt;!-- unplugin from UnJS --&gt;
  &lt;line x1=&quot;80&quot; y1=&quot;400&quot; x2=&quot;95&quot; y2=&quot;445&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,50;50,0;0,50&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;105&quot; cy=&quot;465&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;105&quot; y=&quot;463&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;un-&lt;/text&gt;
  &lt;text x=&quot;105&quot; y=&quot;470&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;plugin&lt;/text&gt;</p>
<p>&lt;!-- jiti from UnJS --&gt;
  &lt;line x1=&quot;50&quot; y1=&quot;400&quot; x2=&quot;35&quot; y2=&quot;435&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,40;40,0;0,40&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;28&quot; cy=&quot;455&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;28&quot; y=&quot;455&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;jiti&lt;/text&gt;</p>
<p>&lt;!-- mlly from UnJS --&gt;
  &lt;line x1=&quot;45&quot; y1=&quot;395&quot; x2=&quot;20&quot; y2=&quot;405&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,30;30,0;0,30&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;8&quot; cy=&quot;415&quot; r=&quot;14&quot; fill=&quot;#00dc82&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.6;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;8&quot; y=&quot;415&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;mlly&lt;/text&gt;</p>
<p>&lt;!-- Analog, TanStack Start from Nitro --&gt;
  &lt;line x1=&quot;135&quot; y1=&quot;356&quot; x2=&quot;135&quot; y2=&quot;380&quot; stroke=&quot;#00dc82&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.4&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,25;25,0;0,25&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;text x=&quot;180&quot; y=&quot;370&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#8899aa&quot; font-family=&quot;system-ui&quot;&gt;Analog, TanStack Start...&lt;/text&gt;</p>
<p>&lt;!-- Vite expansions (other frameworks) --&gt;
  &lt;line x1=&quot;250&quot; y1=&quot;235&quot; x2=&quot;250&quot; y2=&quot;275&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,45;45,0;0,45&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;text x=&quot;250&quot; y=&quot;295&quot; text-anchor=&quot;middle&quot; font-size=&quot;6&quot; fill=&quot;#8899aa&quot; font-family=&quot;system-ui&quot;&gt;React, Svelte,&lt;/text&gt;
  &lt;text x=&quot;250&quot; y=&quot;308&quot; text-anchor=&quot;middle&quot; font-size=&quot;6&quot; fill=&quot;#8899aa&quot; font-family=&quot;system-ui&quot;&gt;Solid, Qwik...&lt;/text&gt;</p>
<p>&lt;!-- Vite to Nuxt connection --&gt;
  &lt;line x1=&quot;215&quot; y1=&quot;215&quot; x2=&quot;85&quot; y2=&quot;285&quot; stroke=&quot;#646cff&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,150;150,0;0,150&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Vitest from VoidZero --&gt;
  &lt;line x1=&quot;400&quot; y1=&quot;190&quot; x2=&quot;465&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,80;80,0;0,80&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;490&quot; cy=&quot;145&quot; r=&quot;28&quot; fill=&quot;#42b883&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;490&quot; y=&quot;150&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vitest&lt;/text&gt;</p>
<p>&lt;!-- Rolldown from VoidZero --&gt;
  &lt;line x1=&quot;360&quot; y1=&quot;240&quot; x2=&quot;360&quot; y2=&quot;290&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,55;55,0;0,55&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;360&quot; cy=&quot;320&quot; r=&quot;28&quot; fill=&quot;#ff6b6b&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;360&quot; y=&quot;325&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Rolldown&lt;/text&gt;</p>
<p>&lt;!-- Oxc from VoidZero --&gt;
  &lt;line x1=&quot;400&quot; y1=&quot;215&quot; x2=&quot;470&quot; y2=&quot;260&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;2&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,90;90,0;0,90&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;500&quot; cy=&quot;285&quot; r=&quot;28&quot; fill=&quot;#f97316&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;500&quot; y=&quot;290&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Oxc&lt;/text&gt;</p>
<p>&lt;!-- Oxc to Rolldown connection --&gt;
  &lt;line x1=&quot;475&quot; y1=&quot;295&quot; x2=&quot;388&quot; y2=&quot;320&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;1.5&quot; stroke-dasharray=&quot;4,4&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.4;0.7;0.4&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;text x=&quot;432&quot; y=&quot;300&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#8899aa&quot; font-family=&quot;system-ui&quot;&gt;powers&lt;/text&gt;</p>
<p>&lt;!-- Rolldown to Vite connection --&gt;
  &lt;line x1=&quot;332&quot; y1=&quot;320&quot; x2=&quot;278&quot; y2=&quot;220&quot; stroke=&quot;#ff6b6b&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,120;120,0;0,120&quot; dur=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Oxc expansions (6 tools) --&gt;
  &lt;!-- Parser --&gt;
  &lt;line x1=&quot;528&quot; y1=&quot;270&quot; x2=&quot;565&quot; y2=&quot;210&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,70;70,0;0,70&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;580&quot; cy=&quot;195&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;580&quot; y=&quot;199&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Parser&lt;/text&gt;</p>
<p>&lt;!-- Linter --&gt;
  &lt;line x1=&quot;528&quot; y1=&quot;280&quot; x2=&quot;575&quot; y2=&quot;250&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,55;55,0;0,55&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;590&quot; cy=&quot;235&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;590&quot; y=&quot;239&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Linter&lt;/text&gt;</p>
<p>&lt;!-- Formatter --&gt;
  &lt;line x1=&quot;528&quot; y1=&quot;285&quot; x2=&quot;575&quot; y2=&quot;285&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,50;50,0;0,50&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;595&quot; cy=&quot;285&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;595&quot; y=&quot;283&quot; text-anchor=&quot;middle&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Forma-&lt;/text&gt;
  &lt;text x=&quot;595&quot; y=&quot;290&quot; text-anchor=&quot;middle&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;tter&lt;/text&gt;</p>
<p>&lt;!-- Transformer --&gt;
  &lt;line x1=&quot;528&quot; y1=&quot;295&quot; x2=&quot;575&quot; y2=&quot;325&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,55;55,0;0,55&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;590&quot; cy=&quot;340&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;590&quot; y=&quot;338&quot; text-anchor=&quot;middle&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Trans-&lt;/text&gt;
  &lt;text x=&quot;590&quot; y=&quot;345&quot; text-anchor=&quot;middle&quot; font-size=&quot;4&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;former&lt;/text&gt;</p>
<p>&lt;!-- Minifier --&gt;
  &lt;line x1=&quot;520&quot; y1=&quot;305&quot; x2=&quot;560&quot; y2=&quot;375&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,80;80,0;0,80&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;575&quot; cy=&quot;390&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;575&quot; y=&quot;394&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Minifier&lt;/text&gt;</p>
<p>&lt;!-- Resolver --&gt;
  &lt;line x1=&quot;515&quot; y1=&quot;310&quot; x2=&quot;545&quot; y2=&quot;420&quot; stroke=&quot;#f97316&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,120;120,0;0,120&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;circle cx=&quot;555&quot; cy=&quot;440&quot; r=&quot;14&quot; fill=&quot;#f97316&quot; opacity=&quot;0.7&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.5;0.7;0.5&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;text x=&quot;555&quot; y=&quot;444&quot; text-anchor=&quot;middle&quot; font-size=&quot;5&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Resolver&lt;/text&gt;</p>
<p>&lt;!-- Web Community label --&gt;
  &lt;text x=&quot;360&quot; y=&quot;470&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#42b883&quot; font-family=&quot;system-ui&quot; opacity=&quot;0.8&quot;&gt;
    → Expanding to the Web Community
  &lt;/text&gt;</p>
<p>&lt;!-- Outer ring pulse --&gt;
  &lt;circle cx=&quot;360&quot; cy=&quot;260&quot; r=&quot;260&quot; fill=&quot;none&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.2&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;260;265;260;275;260&quot; dur=&quot;5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.15;0.2;0.08;0.2&quot; dur=&quot;5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h2>Vite — The Revolution That Started from Vue</h2>
<p>As you know, Vite is now essential in modern frontend development.</p>
<p>Originally, Vite started as <strong><a href="https://github.com/vitejs/vite/commit/820c2cfbefd376b7be2d0ba5ad1fd39d3e45347e" target="_blank" rel="noopener noreferrer">vue-dev-server</a></strong>.<br>
This was <a href="https://github.com/yyx990803" target="_blank" rel="noopener noreferrer">Evan You</a>&#39;s idea to use browser&#39;s Native ESM for a no-bundler development setup.</p>
<p>Then it was renamed to Vite as a No-bundle Dev Server for Vue Single-File Components, and in v2, the core became framework agnostic, positioning it as &quot;Next Generation Frontend Tooling.&quot;</p>
<p>Yes. <strong>Vite started from Vue.js, continued to evolve, and has now expanded beyond Vue.js as a frontend tool.</strong></p>
<h2>Volar.js — Language Tools Framework</h2>
<p>I mentioned earlier that Vue.js has a language aspect and excellent language tools.</p>
<p>Vue.js has had several language tool implementations in the past, but now <a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener noreferrer">vuejs/language-tools</a> is the official language tool.</p>
<p>Originally, it started as Volar, a VSCode extension for Vue.js, as a community member&#39;s personal project.<br>
Volar evolved over 2 years of immense effort to reach v1.0 and became Vue.js&#39;s official recommended tool.</p>
<p>From there, the core functionality independent of Vue.js was extracted as <strong><a href="https://volarjs.dev" target="_blank" rel="noopener noreferrer">Volar.js</a></strong>, and Vue.js&#39;s language tools became vuejs/language-tools.</p>
<p>Volar.js is now spreading as the foundation for language tools in <strong><a href="https://astro.build" target="_blank" rel="noopener noreferrer">Astro</a></strong>, <strong>MDX</strong>, and <strong><a href="https://analogjs.org" target="_blank" rel="noopener noreferrer">Analog</a></strong> (Angular&#39;s meta-framework).</p>
<h2>UnJS — Ecosystem Expanding from Nuxt</h2>
<p><a href="https://unjs.io" target="_blank" rel="noopener noreferrer">UnJS</a> stands for Unified JavaScript or Unleash JavaScript — a collection of JavaScript libraries.</p>
<p>The concept is: one purpose per library, high quality, works in any environment, and collaborative.</p>
<p>UnJS was developed by <a href="https://github.com/pi0" target="_blank" rel="noopener noreferrer">Pooya Parsa</a>, who was a lead engineer for Nuxt2, as a separate ecosystem from Nuxt. The server engine extracted during Nuxt3 development and libraries Pooya had created were rebranded as the UnJS organization.</p>
<p>UnJS and Nuxt collaborate closely, and many Nuxt features are now implemented by UnJS.<br>
Notable examples include the HTTP framework <strong><a href="https://h3.unjs.io" target="_blank" rel="noopener noreferrer">h3</a></strong> and the server engine <strong><a href="https://nitro.build" target="_blank" rel="noopener noreferrer">Nitro</a></strong> built on top of it.<br>
Nitro is also used in <strong><a href="https://analogjs.org" target="_blank" rel="noopener noreferrer">Analog</a></strong> and others.</p>
<h2>ecosystem-ci — Shared Testing Mechanisms</h2>
<p>Vue.js has a vast ecosystem.<br>
State management libraries, UI frameworks, routers, testing libraries — truly many.</p>
<p>The Vue2 to Vue3 upgrade made ecosystem adoption a major challenge.<br>
So a mechanism for integration testing of these downstream ecosystems was introduced.</p>
<p>This originally came from Vite&#39;s integration testing with Svelte, and the mechanism was adopted in Vue.js.<br>
And again, beyond Vue.js/Vite, similar mechanisms have been adopted in <strong><a href="https://svelte.dev" target="_blank" rel="noopener noreferrer">Svelte</a></strong>, <strong><a href="https://astro.build" target="_blank" rel="noopener noreferrer">Astro</a></strong>, <strong><a href="https://oxc.rs" target="_blank" rel="noopener noreferrer">Oxc</a></strong>, and more.</p>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 120&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;voidzeroGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#f59e0b&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#f59e0b&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;0%;100%;0%&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;100%;200%;100%&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;50&quot; text-anchor=&quot;middle&quot; font-size=&quot;28&quot; font-weight=&quot;bold&quot; fill=&quot;url(#voidzeroGrad)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    VoidZero — Foundation for the Next Era
  &lt;/text&gt;
  &lt;text x=&quot;300&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#8899aa&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    A new vision uniting Vite, Vitest, Rolldown, and Oxc
  &lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>VoidZero and the Challenge of a Unified Toolchain</h1>
<p>The expansion of the Vue.js ecosystem has led to an even greater challenge.</p>
<h2>The Fragmentation Problem</h2>
<p>Through developing Vite, <a href="https://github.com/yyx990803" target="_blank" rel="noopener noreferrer">Evan You</a> faced challenges that the entire JavaScript ecosystem shares.</p>
<blockquote>
<p>&quot;Every application relies on a myriad of third-party dependencies, and configuring them to work well together remains one of the most daunting tasks.&quot;</p>
<p>— https://voidzero.dev/posts/announcing-voidzero-inc</p>
</blockquote>
<p>While Vite greatly improved the developer experience, internally it still had various dependencies, requiring abstractions and workarounds to smooth over inconsistencies. Duplicated parsing and serialization costs across different tools became bottlenecks.</p>
<h2>The Birth of VoidZero</h2>
<p>In 2024, <a href="https://github.com/yyx990803" target="_blank" rel="noopener noreferrer">Evan You</a> founded <a href="https://voidzero.dev" target="_blank" rel="noopener noreferrer">VoidZero</a>.<br>
With a vision: <strong>&quot;Building the next-generation unified JavaScript toolchain.&quot;</strong></p>
<p>https://voidzero.dev/posts/announcing-voidzero-inc</p>
<p>VoidZero&#39;s vision centers on building an integrated development stack with four characteristics:</p>
<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg viewBox=&quot;0 0 500 300&quot; style=&quot;max-width: 500px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;boxGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;100%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;</p>
<p>&lt;!-- Unified --&gt;
  &lt;rect x=&quot;20&quot; y=&quot;20&quot; width=&quot;220&quot; height=&quot;60&quot; rx=&quot;10&quot; fill=&quot;url(#boxGrad)&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.9;1;0.9&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/rect&gt;
  &lt;text x=&quot;130&quot; y=&quot;45&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Unified&lt;/text&gt;
  &lt;text x=&quot;130&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#ccc&quot; font-family=&quot;system-ui&quot;&gt;Same AST, resolver, module interop&lt;/text&gt;</p>
<p>&lt;!-- High Performance --&gt;
  &lt;rect x=&quot;260&quot; y=&quot;20&quot; width=&quot;220&quot; height=&quot;60&quot; rx=&quot;10&quot; fill=&quot;url(#boxGrad)&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.9;1;0.9&quot; dur=&quot;2s&quot; begin=&quot;0.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/rect&gt;
  &lt;text x=&quot;370&quot; y=&quot;45&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;High Performance&lt;/text&gt;
  &lt;text x=&quot;370&quot; y=&quot;65&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#ccc&quot; font-family=&quot;system-ui&quot;&gt;Maximum parallelization in Rust&lt;/text&gt;</p>
<p>&lt;!-- Composable --&gt;
  &lt;rect x=&quot;20&quot; y=&quot;100&quot; width=&quot;220&quot; height=&quot;60&quot; rx=&quot;10&quot; fill=&quot;url(#boxGrad)&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.9;1;0.9&quot; dur=&quot;2s&quot; begin=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/rect&gt;
  &lt;text x=&quot;130&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Composable&lt;/text&gt;
  &lt;text x=&quot;130&quot; y=&quot;145&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#ccc&quot; font-family=&quot;system-ui&quot;&gt;Independent components&lt;/text&gt;</p>
<p>&lt;!-- Runtime Agnostic --&gt;
  &lt;rect x=&quot;260&quot; y=&quot;100&quot; width=&quot;220&quot; height=&quot;60&quot; rx=&quot;10&quot; fill=&quot;url(#boxGrad)&quot; opacity=&quot;0.9&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.9;1;0.9&quot; dur=&quot;2s&quot; begin=&quot;1.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/rect&gt;
  &lt;text x=&quot;370&quot; y=&quot;125&quot; text-anchor=&quot;middle&quot; font-size=&quot;14&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Runtime Agnostic&lt;/text&gt;
  &lt;text x=&quot;370&quot; y=&quot;145&quot; text-anchor=&quot;middle&quot; font-size=&quot;10&quot; fill=&quot;#ccc&quot; font-family=&quot;system-ui&quot;&gt;Consistent experience across all JS envs&lt;/text&gt;</p>
<p>&lt;!-- Arrow down to projects --&gt;
  &lt;line x1=&quot;250&quot; y1=&quot;170&quot; x2=&quot;250&quot; y2=&quot;200&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;2&quot; marker-end=&quot;url(#arrowhead)&quot;/&gt;</p>
<p>&lt;!-- Project boxes --&gt;
  &lt;rect x=&quot;40&quot; y=&quot;210&quot; width=&quot;100&quot; height=&quot;40&quot; rx=&quot;5&quot; fill=&quot;#646cff&quot;/&gt;
  &lt;text x=&quot;90&quot; y=&quot;235&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vite&lt;/text&gt;</p>
<p>&lt;rect x=&quot;160&quot; y=&quot;210&quot; width=&quot;100&quot; height=&quot;40&quot; rx=&quot;5&quot; fill=&quot;#42b883&quot;/&gt;
  &lt;text x=&quot;210&quot; y=&quot;235&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Vitest&lt;/text&gt;</p>
<p>&lt;rect x=&quot;280&quot; y=&quot;210&quot; width=&quot;100&quot; height=&quot;40&quot; rx=&quot;5&quot; fill=&quot;#ff6b6b&quot;/&gt;
  &lt;text x=&quot;330&quot; y=&quot;235&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Rolldown&lt;/text&gt;</p>
<p>&lt;rect x=&quot;400&quot; y=&quot;210&quot; width=&quot;60&quot; height=&quot;40&quot; rx=&quot;5&quot; fill=&quot;#f97316&quot;/&gt;
  &lt;text x=&quot;430&quot; y=&quot;235&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Oxc&lt;/text&gt;</p>
<p>&lt;!-- Open Source badge --&gt;</p>
<p>&lt;text x=&quot;250&quot; y=&quot;280&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#42b883&quot; font-family=&quot;system-ui&quot;&gt;Open Source&lt;/text&gt;
&lt;/svg&gt;</p>
<p>&lt;/div&gt;</p>
<h2>Oxc — High-Speed Compiler Written in Rust</h2>
<p><a href="https://oxc.rs/" target="_blank" rel="noopener noreferrer">Oxc (JavaScript Oxidation Compiler)</a> is a JavaScript/TypeScript toolchain written in Rust, led by <a href="https://github.com/boshen" target="_blank" rel="noopener noreferrer">Boshen</a>.</p>
<p>A comprehensive toolset including parser, linter (Oxlint), formatter (Oxfmt), transformer, minifier, and resolver, achieving <strong>overwhelming performance</strong>:</p>
<ul>
<li><p><strong>Oxlint</strong>: 50-100x faster than ESLint</p>
</li>
<li><p><strong>Oxfmt</strong>: 35x faster than Prettier</p>
</li>
<li><p><strong>oxc-resolver</strong>: 30x faster than webpack&#39;s enhanced-resolve</p>
</li>
<li><p><strong>transformer</strong>: 40x faster than Babel</p>
</li>
</ul>
<p>https://oxc.rs/</p>
<p>Oxlint 1.0 was released as stable in June 2025, with over 5,200 early adopters including Shopify and Airbnb.</p>
<h2>Rolldown — Rust-Based Bundler</h2>
<p><a href="https://rolldown.rs/" target="_blank" rel="noopener noreferrer">Rolldown</a> is a Rust-based bundler developed by VoidZero, achieving <strong>10-30x faster performance than Rollup</strong> with a Rollup-compatible API.</p>
<p>https://vite.dev/blog/announcing-vite8</p>
<p>On March 12, 2026, <strong>Vite 8.0</strong> was released as stable, integrating Rolldown as Vite&#39;s single Rust-based bundler.<br>
With this, Vite 8 moves from the previous esbuild + Rollup dual setup to a unified Rolldown-centered architecture.</p>
<p>During the preview and beta phases, real projects reported impressive performance improvements:</p>
<ul>
<li><p>Linear: Production build time 46 sec → 6 sec</p>
</li>
<li><p>Ramp: 57% build time reduction</p>
</li>
<li><p>Mercedes-Benz.io: Up to 38% reduction</p>
</li>
<li><p>Beehiiv: 64% reduction</p>
</li>
</ul>
<p>This initiative makes Vite the entry point to an end-to-end toolchain maintained by closely collaborating teams: <strong>build tool (Vite), bundler (Rolldown), and compiler (Oxc)</strong>.</p>
<h2>Vite+ — Unified Toolchain</h2>
<p>On March 13, 2026, VoidZero also released <strong>Vite+ Alpha</strong>.<br>
As a superset of Vite, it centers the workflow around the global <code>vp</code> CLI and a project-local <code>vite-plus</code> package, providing the tools developers need as a single, unified toolchain:</p>
<ul>
<li><p><code>vp create</code> — Project and monorepo scaffolding</p>
</li>
<li><p><code>vp env</code> — Global and per-project Node.js runtime management</p>
</li>
<li><p><code>vp install</code> — Dependency installation through the selected package manager</p>
</li>
<li><p><code>vp dev</code> — Vite development server</p>
</li>
<li><p><code>vp check</code> — Type checking, linting, and formatting in one command</p>
</li>
<li><p><code>vp test</code> — Testing via Vitest</p>
</li>
<li><p><code>vp build</code> — Production builds</p>
</li>
<li><p><code>vp run</code> — Script and monorepo task execution via Vite Task</p>
</li>
<li><p><code>vp pack</code> — Library and CLI packaging via tsdown</p>
</li>
</ul>
<p>https://voidzero.dev/posts/announcing-vite-plus-alpha</p>
<p>https://viteplus.dev/</p>
<h2>Trust and Commitment to Open Source</h2>
<p>What&#39;s crucial here is VoidZero&#39;s <strong>stance on open source</strong>.</p>
<blockquote>
<p>&quot;The trust the community has placed in Vite&quot; prompted deep reflection on its future direction.</p>
<p>— https://voidzero.dev/posts/announcing-voidzero-inc</p>
</blockquote>
<p>VoidZero clearly declares:</p>
<blockquote>
<p>&quot;Everything we&#39;ve open-sourced will remain open source. Vite, Vitest, Rolldown, and Oxc will remain open source.&quot;</p>
<p>— https://voidzero.dev/posts/announcing-voidzero-inc</p>
</blockquote>
<p>With that Vite+ Alpha release, Vite+ itself was also fully open-sourced under the MIT license.</p>
<p>And importantly, VoidZero <strong>provides first-class support for Vue.js</strong>. These tools that started from Vue.js will continue to deliver the best experience for the Vue.js ecosystem.</p>
<p>These projects are already adopted by major organizations including OpenAI, Google, Apple, Microsoft, and Shopify. Their reliability is proven.</p>
<p>https://github.com/vitejs/companies-using-vite</p>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 100&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;titleGrad4&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;0%;100%;0%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;100%;200%;100%&quot; dur=&quot;6s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot; font-size=&quot;32&quot; font-weight=&quot;bold&quot; fill=&quot;url(#titleGrad4)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    4 / 4 — Community
  &lt;/text&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;85&quot; x2=&quot;500&quot; y2=&quot;85&quot; stroke=&quot;url(#titleGrad4)&quot; stroke-width=&quot;2&quot; stroke-dasharray=&quot;5,5&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dashoffset&quot; values=&quot;0;-20&quot; dur=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Vue.js is Community</h1>
<p>The final character. This is an inseparable, essential element of Vue.js.</p>
<p>&lt;div style=&quot;text-align: center; margin: 3rem 0;&quot;&gt;
&lt;svg viewBox=&quot;0 0 500 320&quot; style=&quot;max-width: 500px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;radialGradient id=&quot;coreGlow&quot; cx=&quot;50%&quot; cy=&quot;50%&quot; r=&quot;50%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883;stop-opacity:0.4&quot;/&gt;
      &lt;stop offset=&quot;70%&quot; style=&quot;stop-color:#42b883;stop-opacity:0.1&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#42b883;stop-opacity:0&quot;/&gt;
    &lt;/radialGradient&gt;
    &lt;linearGradient id=&quot;nodeGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;100%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#64d4a3&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
    &lt;/linearGradient&gt;
    &lt;filter id=&quot;glow&quot;&gt;
      &lt;feGaussianBlur stdDeviation=&quot;2&quot; result=&quot;coloredBlur&quot;/&gt;
      &lt;feMerge&gt;
        &lt;feMergeNode in=&quot;coloredBlur&quot;/&gt;
        &lt;feMergeNode in=&quot;SourceGraphic&quot;/&gt;
      &lt;/feMerge&gt;
    &lt;/filter&gt;
  &lt;/defs&gt;</p>
<p>&lt;!-- Background pulse --&gt;
  &lt;circle cx=&quot;250&quot; cy=&quot;160&quot; r=&quot;80&quot; fill=&quot;url(#coreGlow)&quot;&gt;
    &lt;animate attributeName=&quot;r&quot; values=&quot;80;100;80&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;</p>
<p>&lt;!-- Network connections between nodes --&gt;
  &lt;!-- Inner ring connections --&gt;
  &lt;line x1=&quot;175&quot; y1=&quot;90&quot; x2=&quot;325&quot; y2=&quot;90&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;175&quot; y1=&quot;90&quot; x2=&quot;130&quot; y2=&quot;180&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; begin=&quot;0.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;325&quot; y1=&quot;90&quot; x2=&quot;370&quot; y2=&quot;180&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; begin=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;130&quot; y1=&quot;180&quot; x2=&quot;190&quot; y2=&quot;260&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; begin=&quot;1.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;370&quot; y1=&quot;180&quot; x2=&quot;310&quot; y2=&quot;260&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; begin=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;190&quot; y1=&quot;260&quot; x2=&quot;310&quot; y2=&quot;260&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;3s&quot; begin=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Connections to center --&gt;
  &lt;line x1=&quot;175&quot; y1=&quot;90&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;325&quot; y1=&quot;90&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; begin=&quot;0.33s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;130&quot; y1=&quot;180&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; begin=&quot;0.66s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;370&quot; y1=&quot;180&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; begin=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;190&quot; y1=&quot;260&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; begin=&quot;1.33s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
  &lt;line x1=&quot;310&quot; y1=&quot;260&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#42b883&quot; stroke-width=&quot;1.5&quot; opacity=&quot;0.6&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,100;100,0&quot; dur=&quot;2s&quot; begin=&quot;1.66s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;</p>
<p>&lt;!-- Outer nodes for depth --&gt;
  &lt;circle cx=&quot;100&quot; cy=&quot;60&quot; r=&quot;6&quot; fill=&quot;#35495e&quot; opacity=&quot;0.4&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.3;0.6;0.3&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;circle cx=&quot;400&quot; cy=&quot;60&quot; r=&quot;5&quot; fill=&quot;#35495e&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;4s&quot; begin=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;circle cx=&quot;60&quot; cy=&quot;240&quot; r=&quot;7&quot; fill=&quot;#35495e&quot; opacity=&quot;0.35&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.25;0.55;0.25&quot; dur=&quot;4s&quot; begin=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;circle cx=&quot;440&quot; cy=&quot;240&quot; r=&quot;5&quot; fill=&quot;#35495e&quot; opacity=&quot;0.3&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.2;0.5;0.2&quot; dur=&quot;4s&quot; begin=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;circle cx=&quot;250&quot; cy=&quot;30&quot; r=&quot;6&quot; fill=&quot;#35495e&quot; opacity=&quot;0.4&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.3;0.6;0.3&quot; dur=&quot;4s&quot; begin=&quot;0.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;
  &lt;circle cx=&quot;250&quot; cy=&quot;300&quot; r=&quot;5&quot; fill=&quot;#35495e&quot; opacity=&quot;0.35&quot;&gt;
    &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.25;0.55;0.25&quot; dur=&quot;4s&quot; begin=&quot;1.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/circle&gt;</p>
<p>&lt;!-- Faint outer connections --&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;60&quot; x2=&quot;175&quot; y2=&quot;90&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.2&quot;/&gt;
  &lt;line x1=&quot;400&quot; y1=&quot;60&quot; x2=&quot;325&quot; y2=&quot;90&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.2&quot;/&gt;
  &lt;line x1=&quot;60&quot; y1=&quot;240&quot; x2=&quot;130&quot; y2=&quot;180&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.2&quot;/&gt;
  &lt;line x1=&quot;440&quot; y1=&quot;240&quot; x2=&quot;370&quot; y2=&quot;180&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.2&quot;/&gt;
  &lt;line x1=&quot;250&quot; y1=&quot;30&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.15&quot;/&gt;
  &lt;line x1=&quot;250&quot; y1=&quot;300&quot; x2=&quot;250&quot; y2=&quot;160&quot; stroke=&quot;#35495e&quot; stroke-width=&quot;0.5&quot; opacity=&quot;0.15&quot;/&gt;</p>
<p>&lt;!-- Central Vue logo simplified --&gt;
  &lt;g filter=&quot;url(#glow)&quot;&gt;
    &lt;polygon points=&quot;250,120 270,155 280,140 250,95 220,140 230,155&quot; fill=&quot;#42b883&quot; opacity=&quot;0.9&quot;&gt;
      &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/polygon&gt;
    &lt;polygon points=&quot;250,120 265,145 250,175 235,145&quot; fill=&quot;#35495e&quot; opacity=&quot;0.9&quot;&gt;
      &lt;animate attributeName=&quot;opacity&quot; values=&quot;0.8;1;0.8&quot; dur=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/polygon&gt;
  &lt;/g&gt;</p>
<p>&lt;!-- Main community nodes --&gt;
  &lt;g filter=&quot;url(#glow)&quot;&gt;
    &lt;circle cx=&quot;175&quot; cy=&quot;90&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
    &lt;circle cx=&quot;325&quot; cy=&quot;90&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; begin=&quot;0.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
    &lt;circle cx=&quot;130&quot; cy=&quot;180&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; begin=&quot;1s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
    &lt;circle cx=&quot;370&quot; cy=&quot;180&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; begin=&quot;1.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
    &lt;circle cx=&quot;190&quot; cy=&quot;260&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; begin=&quot;2s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
    &lt;circle cx=&quot;310&quot; cy=&quot;260&quot; r=&quot;14&quot; fill=&quot;#42b883&quot; opacity=&quot;0.85&quot;&gt;
      &lt;animate attributeName=&quot;r&quot; values=&quot;14;16;14&quot; dur=&quot;3s&quot; begin=&quot;2.5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/circle&gt;
  &lt;/g&gt;</p>
<p>&lt;!-- Node labels --&gt;</p>
<p>&lt;text x=&quot;175&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;text x=&quot;325&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;text x=&quot;130&quot; y=&quot;180&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;text x=&quot;370&quot; y=&quot;180&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;text x=&quot;190&quot; y=&quot;260&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;text x=&quot;310&quot; y=&quot;260&quot; text-anchor=&quot;middle&quot; dominant-baseline=&quot;central&quot; font-size=&quot;8&quot; fill=&quot;#fff&quot; font-weight=&quot;bold&quot; font-family=&quot;system-ui&quot;&gt;Dev&lt;/text&gt;
&lt;/svg&gt;</p>
<p>&lt;/div&gt;</p>
<h2>What Emerges from Community</h2>
<p>Many of the projects I&#39;ve introduced started as personal projects by community members.</p>
<ul>
<li><p><strong>Volar</strong> — From <a href="https://github.com/johnsoncodehk" target="_blank" rel="noopener noreferrer">Johnson Chu</a>&#39;s personal project to Vue.js official language tools</p>
</li>
<li><p><strong>Oxc</strong> — The Rust parser <a href="https://github.com/boshen" target="_blank" rel="noopener noreferrer">Boshen</a> started became VoidZero&#39;s core technology</p>
</li>
<li><p><strong>UnJS</strong> — <a href="https://github.com/pi0" target="_blank" rel="noopener noreferrer">Pooya Parsa</a>&#39;s work outside Nuxt became an independent ecosystem</p>
</li>
</ul>
<p>This isn&#39;t coincidence.<br>
The Vue.js community has the <strong>power for individual passion to move the entire ecosystem</strong>.</p>
<h2>Chain of Trust</h2>
<p>The phenomenon of Vue.js&#39;s ecosystem influencing other frameworks and tools cannot be explained by technical superiority alone.</p>
<p>It&#39;s <strong>trust</strong>.</p>
<p>As <a href="https://github.com/yyx990803" target="_blank" rel="noopener noreferrer">Evan You</a> stated when founding VoidZero:</p>
<blockquote>
<p>&quot;The trust the community has placed in Vite&quot;</p>
<p>— https://voidzero.dev/posts/announcing-voidzero-inc</p>
</blockquote>
<p>This trust wasn&#39;t built overnight.<br>
Over 10 years of consistent commitment to open source, consideration for backward compatibility, and listening to the community&#39;s voice.</p>
<p>As a result, tools that started from Vue.js are used in the React ecosystem, in Svelte, in Solid.</p>
<h2>Contribution to the Web Community</h2>
<p>This isn&#39;t just a Vue.js community story.<br>
It&#39;s a contribution to the <strong>entire Web developer community</strong>.</p>
<ul>
<li><p><a href="https://vite.dev" target="_blank" rel="noopener noreferrer">Vite</a> changed the development experience for all frameworks beyond Vue.js</p>
</li>
<li><p><a href="https://volarjs.dev" target="_blank" rel="noopener noreferrer">Volar.js</a> enabled language support for Astro and MDX</p>
</li>
<li><p><a href="https://nitro.build" target="_blank" rel="noopener noreferrer">Nitro</a> was adopted by meta-frameworks beyond Nuxt</p>
</li>
<li><p><a href="https://oxc.rs" target="_blank" rel="noopener noreferrer">Oxc</a> and <a href="https://rolldown.rs" target="_blank" rel="noopener noreferrer">Rolldown</a> are contributing to speeding up the entire JavaScript toolchain</p>
</li>
</ul>
<p><strong>Innovation born in one community spreads across boundaries to the entire Web.</strong></p>
<p>This is the true power of open source, and the value that the Vue.js community embodies, in my view.</p>
<hr>
<p>&lt;div style=&quot;text-align: center; margin: 4rem 0 2rem;&quot;&gt;
&lt;svg viewBox=&quot;0 0 600 150&quot; style=&quot;max-width: 600px; width: 100%;&quot;&gt;
  &lt;defs&gt;
    &lt;linearGradient id=&quot;finGrad&quot; x1=&quot;0%&quot; y1=&quot;0%&quot; x2=&quot;100%&quot; y2=&quot;0%&quot;&gt;
      &lt;stop offset=&quot;0%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;stop offset=&quot;25%&quot; style=&quot;stop-color:#35495e&quot;/&gt;
      &lt;stop offset=&quot;50%&quot; style=&quot;stop-color:#646cff&quot;/&gt;
      &lt;stop offset=&quot;75%&quot; style=&quot;stop-color:#f59e0b&quot;/&gt;
      &lt;stop offset=&quot;100%&quot; style=&quot;stop-color:#42b883&quot;/&gt;
      &lt;animate attributeName=&quot;x1&quot; values=&quot;-100%;100%&quot; dur=&quot;5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
      &lt;animate attributeName=&quot;x2&quot; values=&quot;0%;200%&quot; dur=&quot;5s&quot; repeatCount=&quot;indefinite&quot;/&gt;
    &lt;/linearGradient&gt;
  &lt;/defs&gt;
  &lt;text x=&quot;300&quot; y=&quot;60&quot; text-anchor=&quot;middle&quot; font-size=&quot;36&quot; font-weight=&quot;bold&quot; fill=&quot;url(#finGrad)&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    Characterize Vue.js
  &lt;/text&gt;
  &lt;text x=&quot;300&quot; y=&quot;100&quot; text-anchor=&quot;middle&quot; font-size=&quot;16&quot; fill=&quot;#666&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;
    Approachable × Language × Ecosystem × Community
  &lt;/text&gt;
  &lt;line x1=&quot;100&quot; y1=&quot;125&quot; x2=&quot;500&quot; y2=&quot;125&quot; stroke=&quot;url(#finGrad)&quot; stroke-width=&quot;3&quot;&gt;
    &lt;animate attributeName=&quot;stroke-dasharray&quot; values=&quot;0,400;400,0;0,400&quot; dur=&quot;4s&quot; repeatCount=&quot;indefinite&quot;/&gt;
  &lt;/line&gt;
&lt;/svg&gt;
&lt;/div&gt;</p>
<h1>Conclusion</h1>
<p>I&#39;ve characterized Vue.js through 4 characters.</p>
<ol>
<li><p><strong>Approachable</strong> — Flexible choices, easy to approach for various use cases</p>
</li>
<li><p><strong>Language</strong> — A domain-specific language for describing UI</p>
</li>
<li><p><strong>Ecosystem</strong> — Starting from Vue.js, expanding beyond its boundaries</p>
</li>
<li><p><strong>Community</strong> — Individual passion drives the ecosystem and builds trust</p>
</li>
</ol>
<p>These aren&#39;t independent characteristics.<br>
They influence and reinforce each other.</p>
<p>Because it&#39;s Approachable, many people join and the community grows.<br>
Because the community grows, language tools and the ecosystem develop.<br>
Because the ecosystem develops, more choices emerge and Approachability increases.</p>
<p>This cycle is the essence of Vue.js&#39;s value, I believe.</p>
<hr>
<p>Vue.js isn&#39;t just a UI framework.<br>
It&#39;s approachable,<br>
a language designed for humans,<br>
an ecosystem that expands beyond boundaries,<br>
and a community connected by trust and passion.</p>
<hr>
<blockquote>
<p>This is merely my personal essay and does not represent the official views of the Vue.js Team.</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Vue.js is not Easy. It is Approachable.</title>
      <link>https://wtrclred.vercel.app/posts/06</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/06</guid>
      <description>abstraction through language and fallacy</description>
      <pubDate>Wed, 24 Dec 2025 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T17:05:07.000Z</dc:date>
      <category>vue</category>
      <category>javascript</category>
      <category>language</category>
      <category>easy</category>
      <category>simple</category>
      <category>architecture</category>
      <category>tradeoff</category>
      <content:encoded><![CDATA[<h1>Vue.js is not Easy. It is Approachable.</h1>
<h2>A Familiar Sound</h2>
<p>Nowadays, marketing terms like Simple/Easy are frequently used.<br>
I don&#39;t intend to mention other specific languages or frameworks this time.<br>
This is purely a story about how to approach Vue.js.</p>
<p>&quot;<strong>Vue.js is Easy</strong>&quot;</p>
<p>Have you ever heard this phrase?<br>
I&#39;ve heard it many times.<br>
And just as often, I&#39;ve heard people say &quot;<strong>Vue.js is bad because it has no rules</strong>.&quot;</p>
<h2>Fallacy</h2>
<p>I started working with Vue.js / Web around Q4 of 2020, and I don&#39;t know what the world was like before that.<br>
I also don&#39;t know when this phrase started becoming popular.<br>
However, based on my current feelings, I always have doubts about whether using this phrase is appropriate.</p>
<p>&quot;<strong>Vue.js is Easy</strong>&quot; is, in my opinion, still correct in limited contexts.<br>
But how many people actually understand those contexts?</p>
<p>Lately, there&#39;s been a trend of &quot;things with rules (not limited to web frameworks),&quot; and they do their own marketing like &quot;〇〇 is △△ (but not Easy).&quot;</p>
<p>And in response to that,</p>
<p>&quot;<strong>Vue.js is bad because it has no rules</strong>&quot;<br>
&quot;<strong>It sacrifices rigidity for short-term simplicity</strong>&quot;<br>
&quot;<strong>Having no rules makes code a mess</strong>&quot;</p>
<p>Such claims emerge.</p>
<p><strong>Abstract marketing gets beaten by abstract marketing</strong>.</p>
<h2>Trade-offs</h2>
<p>As I mentioned earlier, &quot;<strong>Vue.js is Easy</strong>&quot; is indeed correct in some contexts.<br>
Since Vue.js has no explicit rules, it&#39;s correct in the sense that <strong>from the framework&#39;s standpoint, it&#39;s not wrong</strong> even without learning any particular rules.</p>
<p>However, actually building a product requires thinking about many things.<br>
And here lies a <strong>trade-off</strong>.</p>
<p>Many people probably approach programming with the preconceived notion that &quot;having rules is better&quot; without considering this trade-off.<br>
Of course, even &quot;having rules is better&quot; has its own trade-offs, but that&#39;s not what I want to discuss today.<br>
Let&#39;s imagine a situation where the conclusion was &quot;having rules is better.&quot;</p>
<p>What should be considered here is:</p>
<ul>
<li><p>What is the rule for (what benefits does having that rule bring)</p>
</li>
<li><p>Is it appropriate for the current (future) team (yourself) (I&#39;m using parentheses because it varies by context)</p>
</li>
<li><p>What trade-offs does that rule create</p>
</li>
</ul>
<p>Web applications are incredibly diverse.<br>
Some simply implement UI for data from the backend and display it,<br>
while others provide rich UI combined with complex DOM operations.</p>
<p>And the rules needed for these are completely different.<br>
<strong>Design is quite ad-hoc, and there is no silver bullet</strong>.</p>
<h2>Who&#39;s Responsible</h2>
<p>What&#39;s important here is &quot;<strong>who takes responsibility for those rules</strong>&quot;.<br>
In the case of web applications, I don&#39;t think it&#39;s necessarily good for the framework to own the rules.<br>
Of course, there may be use cases where it&#39;s good for the framework to own the rules.</p>
<p>However, having &quot;the framework&quot;—the foundation of our work—take on this responsibility involves trade-offs.<br>
When the framework takes responsibility here, in most cases, compliance with it is expected.<br>
Even if it&#39;s not important for our product.</p>
<p>At the same time, we should also consider other trade-offs that are lost as a result (such as learning costs).<br>
Additionally, I want to note that &quot;<strong>who takes responsibility for those rules</strong>&quot; and &quot;whether rules are necessary&quot; are completely different matters.</p>
<h2>Abstraction Through Language</h2>
<p>I&#39;ve covered the essential points I wanted to make so far.<br>
However, we can&#39;t help but want to play word games with definitive statements.</p>
<p>If &quot;<strong>Vue.js is Easy</strong>&quot; tends to contain negative fallacies and isn&#39;t appropriate, what should we say instead?<br>
I love the word &quot;<strong>Approachable</strong>&quot; that appears in Vue.js&#39;s official documentation.</p>
<p>https://vuejs.org/</p>
<p>As I mentioned earlier, &quot;Vue.js is Easy&quot; is correct within the scope of &quot;the framework&#39;s responsibility,&quot; but when extended to &quot;implementing (and maintaining) web applications,&quot; it may lead to incorrect judgments.</p>
<p>If there&#39;s some kind of challenge (demand), thinking about conventions and design is a given.<br>
The word <strong>Easy</strong> might give the fallacy that you can build something good without thinking about this.</p>
<p>Vue.js is <strong>Approachable</strong>.<br>
It doesn&#39;t get in the way when you&#39;re thinking about something.</p>
<p>However, in exchange, the implementer bears a bit of responsibility.<br>
This is a trade-off.</p>
<hr>
<p>This is merely my personal essay and not an official view of the Vue.js Team.</p>
]]></content:encoded>
    </item>
    <item>
      <title>What is Vue.js? It&apos;s just a language lol</title>
      <link>https://wtrclred.vercel.app/posts/05</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/05</guid>
      <description>Thinking about Vue.js and programming languages from the perspective of DSL</description>
      <pubDate>Tue, 23 Dec 2025 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T09:22:06.000Z</dc:date>
      <category>vue</category>
      <category>javascript</category>
      <category>language</category>
      <category>compiler</category>
      <category>dsl</category>
      <content:encoded><![CDATA[<h1>Premise</h1>
<ul>
<li><p>What I discuss here is not an official view.</p>
</li>
<li><p>I will talk about &quot;languages,&quot; but the discussion is heavily tailored to what I want to convey. Reality is not that simple.</p>
</li>
<li><p>This is merely presenting one perspective—&quot;you can also look at it this way.&quot;</p>
</li>
</ul>
<p>Related: <a href="https://wtrclred.vercel.app/posts/07">Characterize Vue.js</a> expands this language-oriented view into a broader characterization of Vue.js.</p>
<h1>Instructions to Computers</h1>
<p>Whether implementing a web application, a native application, or a browser, everything is an instruction to a computer.
And all of it can be expressed in binary.</p>
<p>However, it is not realistic for humans to write binary directly.
This is like forcing a Japanese speaker to communicate in Morse code.
While it can serve as a means of transmitting meaning, it is not practical.</p>
<h1>The Abstraction of Programming Languages</h1>
<p>Programming languages can be broadly classified into two categories.</p>
<p><strong>General-purpose programming languages</strong> are languages for describing general computations not limited to a specific domain.
Rust, TypeScript, JavaScript, Python, C, Haskell, and others fall into this category.</p>
<p>On the other hand, <strong>domain-specific languages</strong> (DSL) are languages specialized for a particular problem domain.
HTML, CSS, SQL, JSON, and others fall into this category. And Vue can also be positioned here.</p>
<h1>Hierarchy of Abstraction</h1>
<h2>Binary and ISA</h2>
<p>ISA (Instruction Set Architecture) defines the instruction set of a CPU.
x86, ARM, RISC-V, and others fall into this category.</p>
<pre><code class="language-asm">ff 43 00 d1
ff 0f 00 b9
48 05 80 52
...
</code></pre>
<p>Writing to registers, arithmetic operations, conditional branching, jumps. All computations are expressed through combinations of these primitive instructions.
if statements, while loops, function calls—all ultimately reduce to these.</p>
<h2>Assembly</h2>
<p>Assembly is binary converted to a human-readable format.</p>
<pre><code class="language-asm">my_program:
  sub   sp, sp, #16
  str   wzr, [sp, #12]
  mov   w8, #42
  str   w8, [sp, #8]
  ldr   w8, [sp, #8]
  mov   w10, #2
...
</code></pre>
<p><code>sub</code> is subtraction, <code>mov</code> is value transfer, <code>jmp</code> is jump.
Through mnemonics, meaning was given to binary sequences.</p>
<p>But is this enough?</p>
<p>We want to give meaningful names to variables. We want to structure conditional branches. We want to describe repetition concisely. We want to group processes into units called functions.
Assembly may be sufficient for computers, but it still places a high cognitive load on humans.</p>
<h2>Evolution to High-Level Languages</h2>
<pre><code class="language-c">int main() {
  int a = 42;
  if (a % 2 == 0) {
    a = 5;
  }
  a = a + 999;
  return a;
}
</code></pre>
<p>With the advent of C (and Fortran), it became possible to write programs in a form closer to human thinking.
Abstractions such as variables, conditional branches, loops, and functions were introduced, significantly reducing the cognitive load on developers.</p>
<p>However, this was not the end.</p>
<p>Automatic memory management, enhanced type systems, object-oriented programming, functional programming, abstraction of concurrent processing...
Every time new requirements emerged, new languages were born.</p>
<p>A language is a kind of &quot;translator.&quot; It converts human intentions into a format that computers can understand.
And in the process of translation, what can be written concisely and what is hidden determines the design philosophy of the language.</p>
<h1>Motivation for Language Design</h1>
<p>Behind the birth of a language, there is always the improvement of developer experience (DX).</p>
<ul>
<li><p><strong>Semantics of syntax</strong>: Providing syntax that corresponds to units of thought, such as if, while, modules, namespaces</p>
</li>
<li><p><strong>Semantics of the target domain</strong>: Java and C++ for OOP, Rust and Go for system programming, HTML/CSS for UI description</p>
</li>
<li><p><strong>Optimization by compilers</strong>: Developers describe intent, and optimization is delegated to the compiler</p>
</li>
</ul>
<p>A language is an accumulation of design decisions about &quot;what to make easy to write&quot; in a specific problem domain.</p>
<h1>Vue.js is a Language</h1>
<p>Now we get to the main topic.</p>
<p>What is Vue.js? Framework, library—there are various ways to describe it, but here I would like to present one perspective.</p>
<p><strong>Vue.js is a domain-specific language for describing Web UI.</strong></p>
<p>Let&#39;s consider what is needed to declaratively describe UI.</p>
<ul>
<li><p>Affinity with web standards: HTML, CSS, JavaScript</p>
</li>
<li><p>Dynamic view description through data binding</p>
</li>
<li><p>Component-based design</p>
</li>
<li><p>Scoped styles</p>
</li>
<li><p>Content passing through slots</p>
</li>
</ul>
<pre><code class="language-vue">&lt;script setup lang=&quot;ts&quot;&gt;
import { ref } from &quot;vue&quot;;
const count = ref(0);
function increment() {
  count.value++;
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button type=&quot;button&quot; @click=&quot;increment&quot;&gt;count is: {{ count }}&lt;/button&gt;
&lt;/template&gt;

&lt;style scoped&gt;
button {
  color: red;
}
&lt;/style&gt;
</code></pre>
<p>This format called SFC (Single File Component) borrows the syntax of HTML/CSS/JavaScript while giving semantics specialized for UI description.</p>
<h1>Practicality and Language Design</h1>
<p>It is possible to design a completely new language. But is that realistic?</p>
<p>Creating a new language means building an ecosystem from scratch.
Editor support, type checkers, linters, formatters, build tools... all of these need to be developed anew.</p>
<p>Vue.js took an incremental approach.</p>
<p><strong>Leverage existing assets.</strong> Enjoy the benefits of the JavaScript ecosystem and TypeScript&#39;s type system as they are.</p>
<p><strong>Maintain mental models.</strong> The script section uses JavaScript semantics directly. It does not compromise the writing experience of HTML and CSS.</p>
<p>This is like designing a new language as a &quot;dialect.&quot;
Without greatly deviating from the grammar of the mother tongue, but introducing unique vocabulary and syntax for specific expressions.</p>
<h1>Optimization by Compilers</h1>
<p>By having a compiler, a language can benefit from optimization.</p>
<p>Developers describe intent, and the compiler generates efficient code.
Through this division of labor, developers can focus on essential problems and are freed from low-level optimization.</p>
<p>The Vue.js compiler has the following optimizations implemented.</p>
<ul>
<li><p><strong>Static Hoisting</strong>: Hoists static nodes outside the rendering function to avoid regeneration</p>
</li>
<li><p><strong>Patch Flags</strong>: Assigns flags to dynamic parts to speed up diff detection</p>
</li>
<li><p><strong>Tree Flattening</strong>: Flattens nested static nodes to reduce traversal costs</p>
</li>
<li><p><strong>Vapor Mode</strong>: A more direct rendering mode without going through the virtual DOM</p>
</li>
</ul>
<p>Details of these are described in the <a href="https://vuejs.org/guide/extras/rendering-mechanism#compiler-informed-virtual-dom" target="_blank" rel="noopener noreferrer">official documentation</a>.</p>
<p>If you look at the output code in the SFC Playground, you can observe what transformations the compiler is performing.</p>
<h1>Challenges</h1>
<p>Designing and maintaining a language comes with significant costs.</p>
<ul>
<li><p>Language specification design: Balance between consistency and expressiveness</p>
</li>
<li><p>Compiler implementation: Balancing correctness and performance</p>
</li>
<li><p>Peripheral tool development: Language server, integration with other ecosystems</p>
</li>
</ul>
<p>Vue.js is already mature in many areas, but there are still things to be done if you look for them.
Opportunities for contribution always exist.</p>
<h1>Conclusion</h1>
<p>From binary to assembly, from assembly to high-level languages.
The history of programming is a history of accumulated abstraction.</p>
<p>Vue.js can also be positioned in this lineage.
A language that, for the problem domain of Web UI, provides semantics for declarative UI description while building on HTML/CSS/JavaScript.</p>
<p>What is Vue.js?</p>
<p>It&#39;s a language.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why I Started Seeking Sponsors</title>
      <link>https://wtrclred.vercel.app/posts/04</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/04</guid>
      <description>The reason why I didn&apos;t publicly seek sponsors for a while and the change in my thinking about it.</description>
      <pubDate>Sat, 14 Sep 2024 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T08:53:41.000Z</dc:date>
      <category>oss</category>
      <content:encoded><![CDATA[<h1>Why I Started Seeking Sponsors</h1>
<h2>Naivety</h2>
<p>First, please take a look at this post:</p>
<p>https://x.com/ubugeeei/status/1745815915353325898</p>
<p>To be honest, for a long time, I really thought this way.<br>
This is because I wanted people to take action for the community rather than donate to me.<br>
I fully supported those who tried to take action, made suggestions, and did everything I could to increase collaborators.</p>
<p>However, I realized there were some naive underlying thoughts and negative effects on the community.</p>
<h2>Avoiding Responsibility</h2>
<p>When you accept money, it naturally creates a sense of responsibility, that &quot;I have to do it.&quot;<br>
I&#39;m the type who tries to eliminate unnecessary pressure as much as possible, but I now see that I should have developed the ability to turn that sense of obligation into a driving force.<br>
Not only was I avoiding responsibility, but I was also neglecting to challenge myself to embrace change.<br>
I reflect that this was an immature attitude.</p>
<h2>Undervaluing Myself</h2>
<p>I have always been pessimistic about myself.<br>
While this attitude has its merits, I realized that it negatively impacts not just myself but also the culture around me.<br>
&quot;I don&#39;t deserve to receive rewards for my activities.&quot; &quot;I need to do more work before asking for anything.&quot; &quot;I must stay humble.&quot; &quot;I can&#39;t afford to be arrogant.&quot;<br>
These were my thoughts.</p>
<h2>Negative Impact on the Culture</h2>
<p>Here&#39;s the main point.<br>
If the issue were just personal, I wouldn&#39;t have written this blog post. But I believe this realization is something everyone involved in growing the community and culture should be aware of.<br>
(I hope this message reaches many people.)</p>
<p>Simply put, such an attitude contributes to the stagnation of the culture.<br>
In my ideal world, everyone who contributes should receive rewards appropriate for their efforts, and they should use that as fuel for their next activities.</p>
<p>These days, we often hear discussions about the lack of compensation for OSS maintainers&#39; efforts.<br>
I strongly believe that the OSS community should have better economic circulation.</p>
<p>Yet, by rejecting compensation, not opening sponsorships, or not having any channel to receive support, I was contradicting this belief.<br>
The more I contributed, the higher I unintentionally raised the barrier to economic circulation, which is harmful to the culture.</p>
<p>In reality, compared to contributors active worldwide, my activities are still minor. Whether I open sponsorships or not may not have a large influence, but even so, I believe I should start receiving some form of compensation, even if it&#39;s small, and actively ask for it to contribute to cultural development.<br>
(Small as it may be, I want to cultivate this culture from within my own sphere.)</p>
<p>Regardless of the amount, the culture of paying for activities should move forward.<br>
The more we reject receiving or making payments, the higher the barrier becomes, and the more people will think, &quot;Well, that&#39;s just the way it is.&quot; Moreover, it perpetuates the common misconception that &quot;open source is free to use.&quot;<br>
This is something that should not happen.</p>
<h2>A Side Story</h2>
<p>The one who made me rethink this issue was <a href="https://github.com/ota-meshi" target="_blank" rel="noopener noreferrer">@ota-meshi</a> (Vue.js Core Team Member).<br>
When Vue.js members from abroad came to Japan, I attended an offline meeting and shared a train ride home with Ota-san, where we discussed this topic.<br>
When I explained my old way of thinking, he told me, &quot;That&#39;s wrong.&quot;<br>
When I reflected on it, I realized he was absolutely right.</p>
<p>Another acquaintance also told me, &quot;Isn&#39;t that just you avoiding responsibility?&quot; and &quot;Aren&#39;t you undervaluing yourself too much?&quot;<br>
I agreed with that as well.</p>
<p>When I think alone, I tend to focus only on myself, and I have a bad habit of becoming overly pessimistic. But this experience reaffirmed the importance of talking to people who are ahead of me, those who can evaluate me objectively, and those who take different actions than I do.</p>
<p>I am grateful to everyone who gave me the opportunity to reconsider.</p>
<hr>
<p>I’ve launched my GitHub Sponsors!<br>
If you&#39;d like to support my work, I would greatly appreciate it!</p>
<p>https://github.com/sponsors/ubugeeei</p>
]]></content:encoded>
    </item>
    <item>
      <title>Instability / Propulsion</title>
      <link>https://wtrclred.vercel.app/posts/03</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/03</guid>
      <description>Embrace your own instability.</description>
      <pubDate>Sat, 13 Jul 2024 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T12:02:04.000Z</dc:date>
      <category>philosophy</category>
      <content:encoded><![CDATA[<h1>Instability / Propulsion</h1>
<p>People, including myself, often seek lasting stability.<br>
We want to maintain good performance at work and consistently make progress in our personal hobbies and interests.</p>
<p>However, I have been thinking a lot lately about whether that is truly the ideal state we should aim for. <br>
Is instability always a bad thing?</p>
<p>I personally have my ups and downs, and when I am not in a good state, I always feel frustrated.<br>
Looking back, I sometimes think, &quot;Oh, if only I had been in a better state at that time, things would have been better.&quot;</p>
<p>But that is a mistake.</p>
<p>Being in a bad state can be a driving force.<br>
It is better to have moments of high velocity rather than being constantly in a flat state.</p>
<p>Instability and propulsion are two sides of the same coin.</p>
<p>I love jazz. In jazz, there are concepts such as dominance and swing.<br>
To put it bluntly, dominance creates an unstable pitch, and swing creates an unstable rhythm.</p>
<p>This non-monotonous instability generates propulsion and creates a strong groove.<br>
And that is the charm of jazz.</p>
<p>Let&#39;s understand and embrace that charm.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Activist as an Information Compiler</title>
      <link>https://wtrclred.vercel.app/posts/02</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/02</guid>
      <description>A more generalized form of accessibility.</description>
      <pubDate>Sun, 16 Jun 2024 00:00:00 GMT</pubDate>
      <dc:date>2026-04-11T12:02:04.000Z</dc:date>
      <category>thinking</category>
      <category>concept</category>
      <category>accessibility</category>
      <category>compiler</category>
      <category>philosophy</category>
      <content:encoded><![CDATA[<h1>Activist as an Information Compiler</h1>
<p>A more generalized form of accessibility.</p>
<h2>Definition</h2>
<p>Activist?</p>
<p>Let&#39;s summarize the definition here.<br>
Here, the term &quot;activist&quot; refers to all individuals who are actively engaged in activities.(Well... This doesn&#39;t explain anything...)</p>
<p>A person working for a company, a person who is active in some communities, an independent OSS developer, etc...<br>
There are many ways to be active, but one thing is common to all of them.</p>
<h2>Form of Information</h2>
<p>Information comes in many forms. Text data, audio data, and of course, what you are thinking.<br>
In some cases, &quot;what you are not thinking&quot; can also be information.</p>
<p>These have deferent priorities, and the way they are handled varies depending on the situation.</p>
<h2>Dynamic and Static Information</h2>
<p>Information can be classified into dynamic and static.</p>
<p>Dynamic:</p>
<ul>
<li><p>Conversation</p>
</li>
<li><p>What you are thinking</p>
</li>
<li><p>etc...</p>
</li>
</ul>
<p>Static:</p>
<ul>
<li><p>Text data</p>
</li>
<li><p>Audio data</p>
</li>
<li><p>etc...</p>
</li>
</ul>
<p>This ways of classifying is quite subjective, but as a rule of thumb, it is the difference between &quot;tampering preparedness&quot; and &quot;tampering traceability&quot;.</p>
<p>And I strongly believe that one of the roles of activists is to &quot;compile dynamic information related to activities into static information&quot;.<br>
This is because static information has many values.</p>
<h2>Values of Static Information</h2>
<h3>Accessibility</h3>
<p>First of all, let&#39;s think about accessibility.<br>
Accessibility here is different from context in web accessibility.<br>
It is a more generalized concept that goes beyond the scope of web content.</p>
<p>As an activist, there are several restrictions on access to information:</p>
<ul>
<li><p>Time constraints</p>
</li>
<li><p>Processing power constraints</p>
</li>
<li><p>Memory constraints</p>
</li>
</ul>
<p>First of all, it&#39;s time. Dynamic information cannot be shared with others across time.<br>
Not everyone is present and talking at the same time.<br>
In the micro context, &quot;meetings&quot; are a good example. Of course, some people cannot participate because the schedule does not match.<br>
In the macro context, &quot;generational change (handover)&quot; and &quot;catching up on new participation&quot; are good examples. It is valuable to have the way of thinking and what has been done at that time statically summarized for knowing the past events and history.</p>
<p>Next is processing power. Human processing power varies from person to person.<br>
Some people understand things quickly, while others do not.<br>
Even if you are in the same place at the same time and share your thoughts, the ability to process that information varies.<br>
Static information that complements this is very valuable.</p>
<p>Finally, memory. Human memory is limited.<br>
It is common to forget, and it is common for information to be altered somewhere.<br>
Moreover, static information that is persistent and resistant to alteration is very valuable as a complement to memory.</p>
<h3>Machine Readability</h3>
<p>This word also appears in the context of web content, but there is an analogy in this story as well.<br>
Dynamic information is difficult for machines to read.</p>
<p>On the other hand, static information is easy for machines to read.<br>
If you dare to mix it with recent hot topics, static information can be input directly into LLM.</p>
<h2>Issues</h2>
<p>We have understood that static information has various values, and that activists have a role in creating that value.</p>
<p>However, the problem is that &quot;the ability to convert dynamic information into static information is different for each person, and training is required to improve&quot;.
This ability is different from the so-called &quot;summarizing ability&quot; or &quot;ability to effectively communicate with others&quot;.</p>
<p>The &quot;compiler&quot; must not change the meaning between input and output.
It means that it is necessary to convert dynamic information into static information without losing as much information as possible.<br>
Thoughts, feelings, atmosphere, flow, and so on at that time.</p>
<p>It is difficult to convert these into static information without excess or deficiency.<br>
But this ability is necessary for us activists. Let&#39;s train ourselves.</p>
]]></content:encoded>
    </item>
    <item>
      <title>React is React, just.</title>
      <link>https://wtrclred.vercel.app/posts/01</link>
      <guid isPermaLink="true">https://wtrclred.vercel.app/posts/01</guid>
      <description>Essay: Is React with React Compiler &quot;Just JavaScript&quot;?</description>
      <pubDate>Sat, 17 Feb 2024 00:00:00 GMT</pubDate>
      <dc:date>2026-04-17T09:22:06.000Z</dc:date>
      <category>react</category>
      <category>javascript</category>
      <category>oss</category>
      <category>semantics</category>
      <category>compiler</category>
      <category>react-compiler</category>
      <category>language</category>
      <content:encoded><![CDATA[<p>I scattered my thoughts on Twitter, so I&#39;ll organize them briefly here.</p>
<p>Follow-up: <a href="https://wtrclred.vercel.app/posts/08">React is React, just. Part 2</a> looks at Meta, Babel, Flow, and React Compiler from this same thread.</p>
<h1>Introduction &amp; Premise</h1>
<p>First, as a premise, I am very much <strong>in favor</strong> of the approach of using a compiler for optimization.
This approach is often effective for improving DX without breaking the interface, and I myself use a framework that emphasizes this.
However, I have some questions about the mental model changes (or unification [^1]) that come with the compiler&#39;s intervention.
I don&#39;t write React regularly and I&#39;m not an expert, so what I&#39;m about to discuss is not so much an opinion about React, but rather curiosity about &quot;how do people who use React feel about this point?&quot; — it&#39;s not about whether it&#39;s right or wrong.
I&#39;ll say it again: I am <strong>in favor</strong>.</p>
<p>Also, these are thoughts I had when the React Compiler was mentioned on https://react.dev/blog — this isn&#39;t based on thorough research into the discussions that have taken place about the React Compiler. This is just an essay.</p>
<p>I welcome answers to the questions I have, but I don&#39;t welcome discussions about whether React Compiler or React itself is good or bad based on this essay.
The merits of a technology cannot be evaluated by taking just this portion.
(That&#39;s what I think, so if there&#39;s any statement in this essay that could be taken that way, please point it out. I&#39;m not trying to spread any judgment.)</p>
<p>My position: Organizing thoughts and questions
Reader&#39;s position: Discussing and responding to thoughts and questions
What I&#39;m not doing: Judging the merits of the technology</p>
<p>The reason I went so far to add this disclaimer isn&#39;t because I didn&#39;t want to be criticized or anything like that —
it&#39;s because the merits of paradigms vary greatly depending on perspective, and there hasn&#39;t been enough discussion to talk about them.
I really think it&#39;s bad when misconceptions spread about the merits of technology, so I want to avoid that.</p>
<h1>Topic</h1>
<p>This is already subjective, but there&#39;s an assertion to some extent that React is &quot;pure JavaScript.&quot;
Here, I think we need to be careful because &quot;pure&quot; can have multiple meanings.</p>
<p>Pure means:
(i) React Components are idempotent JavaScript
(ii) React Components are Just JavaScript</p>
<p>What I was particularly curious about this time was: &quot;With the arrival of React Compiler, it seems like (ii) can no longer be satisfied — how do you feel about that?&quot;
This is based on the assertion that both were satisfied before React Compiler came along.</p>
<p>Regarding (i), React Compiler is actually an optimization implementation based on the rule that &quot;components should be idempotent,&quot; so naturally this can still be said after React Compiler&#39;s arrival. I&#39;ll call the semantics being considered in (i) &quot;React semantics.&quot;</p>
<p>The problem is (ii). The assertion is that React Compiler doesn&#39;t change React semantics, but &quot;it does change JS semantics.&quot;
I&#39;m interested in how people using React feel about this. That&#39;s the topic this time.</p>
<hr>
<h1>About what &quot;it changes JS semantics&quot; means</h1>
<p>Let&#39;s consider a component like the following.</p>
<p>This is actually code that appeared <a href="https://youtu.be/qOQClO3g8-Y?t=419" target="_blank" rel="noopener noreferrer">at React Advanced 2023&#39;s talk about React Forget</a>. (Partially omitted)</p>
<pre><code class="language-tsx">function VideoTag({ heading, video, filter }) {
  const filteredVideos = [];
  for (const video of videos) {
    if (applyFilter(video, filter)) {
      filteredVideos.push(video);
    }
  }

  if (filteredVideos.length === 0) {
    return &lt;NoVideos /&gt;;
  }

  return (
    &lt;&gt;
      &lt;Heading
        heading={heading}
        count={filteredVideos.length}
      /&gt;
      &lt;VideoList videos={filterdVideos}&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>When React users see code like this, they&#39;ll notice the unnecessary computation and optimize it.</p>
<pre><code class="language-tsx">function VideoTag({ heading, video, filter }) {
  const filteredVideos = useMemo(() =&gt; {
    const filteredVideos = [];
    for (const video of videos) {
      if (applyFilter(video, filter)) {
        filteredVideos.push(video);
      }
    }
    return filteredVideos;
  }, [videos, filters])


  if (filteredVideos.length === 0) {
    return &lt;NoVideos /&gt;;
  }

  return ...;
}
</code></pre>
<p>Reference: <a href="https://youtu.be/qOQClO3g8-Y?t=196" target="_blank" rel="noopener noreferrer">Understanding Idiomatic React – Joe Savona, Mofei Zhang, React Advanced 2023 t=196</a></p>
<p>However, with the arrival of React Compiler, this is about to change significantly.
React Compiler analyzes this code (the one without useMemo) and transforms it into <strong>JavaScript code</strong> that performs memoization.</p>
<pre><code class="language-tsx">function VideoTab(t36) {
  const $ = useMemoCache(12);
  const { heading, videos, filter } = t36;
  let filteredVideos;

  if ($[0] !== videos || $[1] !== filter) {
    filteredVideos = [];

    for (const video of videos) {
      if (applyFilter(video, filter)) {
        filteredVideos.push(video);
      }
    }
    $[0] = videos;
    $[1] = filter;
    $[2] = filteredVideos;

  } else {
    filteredVideos = $[2];
  }

  if (filteredVideos.length === 0) {
    return &lt;NoVideos /&gt;;
  }

  return ...;
}
</code></pre>
<p>Reference: <a href="https://youtu.be/qOQClO3g8-Y?t=497" target="_blank" rel="noopener noreferrer">Understanding Idiomatic React – Joe Savona, Mofei Zhang, React Advanced 2023 (t=497)</a></p>
<p>This means it&#39;s &quot;optimization of JavaScript code (output code) in terms of React semantics,&quot; and &quot;JavaScript semantics&quot; itself has changed.</p>
<p>A JavaScript function is something that, when written like:</p>
<pre><code class="language-js">function Func(n) {
  let list = [];
  for (let i = 0; i &lt; n; i++) {
    list.push(i);
  }
  return list;
}
</code></pre>
<p>will always be executed regardless of its result.</p>
<p>But React Components after React Compiler are not like that.
<strong>On the surface it looks like a JS function, but it&#39;s no longer Just a JS function</strong>, it seems.</p>
<pre><code class="language-tsx">function VideoTag({ heading, video, filter }) {
  const filteredVideos = [];
  for (const video of videos) {
    if (applyFilter(video, filter)) {
      filteredVideos.push(video);
    }
  }

 ...
}
</code></pre>
<p>Even though you wrote this, the number of function calls and the number of times the written process actually executes don&#39;t match.
Such evaluation rules don&#39;t exist in JavaScript — they&#39;re the result of React Compiler optimizing based on React semantics.</p>
<p>Does this convey what I mean by &quot;with the arrival of React Compiler, React Components are no longer Just JavaScript&quot;?</p>
<hr>
<h1>The semantics have doubled, but the syntax remains one</h1>
<p>Setting aside the discussion of whether we should use both &quot;React semantics&quot; and &quot;JS semantics&quot; when actually implementing Components in React,</p>
<pre><code class="language-tsx">function VideoTag({ heading, video, filter }) {
  const filteredVideos = [];
  for (const video of videos) {
    if (applyFilter(video, filter)) {
      filteredVideos.push(video);
    }
  }

 ...
}
</code></pre>
<p>It&#39;s a fact that when looking at this function, two semantics exist. (React semantics and JS semantics)
Sometimes the number of function calls and the number of body executions may match, and sometimes they may not.
I don&#39;t know if this will be a burden for developers or learners, but the possibility isn&#39;t zero.</p>
<h3>A note about the advantages related to syntax</h3>
<p>Until now, I&#39;ve mainly discussed the semantics of a function like this:</p>
<pre><code class="language-tsx">function VideoTag({ heading, video, filter }) {
  const filteredVideos = [];
  for (const video of videos) {
    if (applyFilter(video, filter)) {
      filteredVideos.push(video);
    }
  }

 ...
}
</code></pre>
<p>But now about syntax.
Regarding syntax, whether before or after React Compiler&#39;s arrival, it&#39;s undoubtedly &quot;Just JavaScript.&quot;
(There&#39;s also the argument that JSX isn&#39;t JS in the first place, but that&#39;s not the main point here, so I&#39;ll skip it)</p>
<p>One advantage often cited for React being Just JavaScript is the ease of static analysis and integration with toolchains.
I personally think these can still be generally claimed. (Whether that means other things are bad is a separate discussion)
Strictly speaking, there&#39;s a possibility that tools where JS semantics are important may partially not be so, but I feel like there are hardly any tools where that becomes important (completely subjective).</p>
<p>However, what I want to note is that distinction.
Before React Compiler, &quot;React is Just JavaScript, therefore ${any evaluation}&quot; could apply to both semantics and syntax. But after React Compiler&#39;s arrival, I think this has become a syntax matter.</p>
<p>When someone says &quot;React is Just JavaScript, therefore ${any evaluation},&quot; without this distinction, there&#39;s a possibility of making incorrect evaluations. I&#39;m not sure how much of a problem this will be, but I thought this might be a newly raised issue.</p>
<p>[^1]: Of course, some people were only thinking in terms of React semantics from the beginning. For them, nothing has particularly changed, but I thought people who were also considering JS semantics would be unified to only think in terms of React semantics (I&#39;m not sure how it actually is)</p>
<h1>So what can we say React Components are?</h1>
<p>If it changes JavaScript&#39;s semantics, it doesn&#39;t seem like we can call it &quot;Just JavaScript.&quot;
So what is it?
.
.
.
.
React is React.
It&#39;s something different that has the same syntax as JavaScript but different meaning.</p>
<p>That&#39;s what I thought, but what do you all think...?</p>
<blockquote>
<p>Essay: Is React JavaScript + Rules, or is it a language?</p>
</blockquote>
<p>https://x.com/ubugeeei/status/1758298301609492584?s=20</p>
<hr>
<p>&lt;details&gt;
&lt;summary&gt;ref:&lt;/summary&gt;</p>
<p>https://x.com/ubugeeei/status/1758296154406821942?s=20</p>
<p>https://x.com/ubugeeei/status/1758308229602529304?s=20</p>
<p>https://x.com/ubugeeei/status/1758309937300808122?s=20</p>
<p>https://x.com/ubugeeei/status/1758458714749911445?s=20</p>
<p>https://x.com/ubugeeei/status/1758467330278035779?s=20</p>
<p>https://x.com/ubugeeei/status/1758508131385266543?s=20</p>
<p>https://x.com/ubugeeei/status/1758521001091191294?s=20</p>
<p>&lt;/details&gt;</p>
<hr>
]]></content:encoded>
    </item>
  </channel>
</rss>