<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Niapya Blog</title><link>https://me.htu.me/blog?lang=en</link><description>A personal portfolio of thoughtful digital products, interfaces, and experiments.</description><language>en</language><lastBuildDate>Wed, 16 Sep 2026 07:10:19 GMT</lastBuildDate><item><title>What have we all done to create a catgirl?</title><link>https://me.htu.me/blog/%E4%B8%BA%E4%BA%86%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%8C%AB%E5%A8%98%E6%88%91%E4%BB%AC%E9%83%BD%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88?lang=en</link><guid isPermaLink="true">https://me.htu.me/blog/%E4%B8%BA%E4%BA%86%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E7%8C%AB%E5%A8%98%E6%88%91%E4%BB%AC%E9%83%BD%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88?lang=en</guid><pubDate>Wed, 16 Sep 2026 07:10:19 GMT</pubDate><description>&lt;p&gt;We built Luna, a cat‑girl‑styled AI companion, by making aggressive trade‑offs: we abandoned platform‑agnosticism in favor of Cloudflare Durable Objects for strongly consistent user and session state, migrated from Next/Nuxt to TanStack Start in six hours, and reduced the tooling stack to a virtual Bash + filesystem backed by R2 and Durable Objects. To handle multi‑modal AI output efficiently we created a custom Dialogue Transport Protocol (DTP) that streams structured messages. These design choices let a three‑person team deliver a fully‑featured, memory‑aware, interactive avatar in under five days.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;origin&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#origin&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Origin&lt;/h2&gt;
&lt;p&gt;My two friends and I formed a team, and the three of us entered Zhihu&apos;s 2026 Hackathon.&lt;/p&gt;
&lt;p&gt;We talked it over and decided to make a catgirl named Luna, inspired by tavern‑style role‑playing chat 🍺, combined with a lobster‑like hands‑on intelligent agent 🦐, plus a nurturing system. Thus she was born.&lt;/p&gt;
&lt;p&gt;In daily life, she is your good companion; every hobby of yours is recorded in her memory. We carefully designed LUNA&apos;s memory system so that the next time you meet, she still remembers what you like.&lt;/p&gt;
&lt;p&gt;At work, she can help you handle complex programs; you can add agent skills, even upload attachments, and let her invoke Python to solve them.&lt;/p&gt;
&lt;p&gt;For entertainment, she is your fun partner. We built a nurturing and achievement system: feed her, give her water, clean her, and intimacy slowly increases.&lt;/p&gt;
&lt;p&gt;We also built a model for LUNA that moves with her. She will think first, then reply line by line, send stickers, make expressions—just as if she were really standing in front of you.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/demo-screenshot.jpg&quot; alt=&quot;Luna demo screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The finished product is here: &lt;a href=&quot;https://luna.htu.me&quot; rel=&quot;noopener noreferrer&quot;&gt;LUNA&lt;/a&gt;. So what did we actually do to create a catgirl?&lt;/p&gt;
&lt;h2 id=&quot;technical-design&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#technical-design&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Technical Design&lt;/h2&gt;
&lt;p&gt;To build this product we put a lot of effort into everything—from architectural decisions and designing the whole Harness to the frontend, etc. We considered everything carefully and implemented many new things.&lt;/p&gt;
&lt;p&gt;Nevertheless, the project was completed in less than 5 days, under great time pressure. During that period we spent roughly 1,000 tokens.&lt;/p&gt;
&lt;p&gt;From the start we defined it as a full‑stack Web App. Choosing a unified front‑back framework was crucial. Our initial architecture used Next.js driven by OpenNext. For many reasons we later migrated to Nuxt, and finally to the TanStack Start framework. I’ll walk through that journey and the trade‑offs.&lt;/p&gt;
&lt;h3 id=&quot;agent-architecture-designed-on-cloudflare-durable-objects&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#agent-architecture-designed-on-cloudflare-durable-objects&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Agent Architecture Designed on Cloudflare Durable Objects&lt;/h3&gt;
&lt;p&gt;From the beginning we treated it as a unified front‑back Web App, initially deploying on OpenNext‑driven Next.js on Cloudflare.&lt;/p&gt;
&lt;p&gt;When we first finished development we found it extremely slow, and the Next.js Adapter API isn’t supported by Cloudflare. That convinced us to refactor, dropping Next.js and moving to the Vue‑based Nuxt.js. Nuxt’s primitives unify KV, Database, and Blob, so Nuxt was a natural choice.&lt;/p&gt;
&lt;p&gt;Cloudflare differs from Vercel; it is more of an all‑in‑one infrastructure platform that controls everything:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SQL database – e.g., D1 SQLite  &lt;/li&gt;
&lt;li&gt;NoSQL database – e.g., KV  &lt;/li&gt;
&lt;li&gt;S3‑like storage – R2 Blob&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Our initial design was simple and aligned with common thinking: store all user data and session data in the SQL database, and put user attachments in R2 Blob.&lt;/p&gt;
&lt;p&gt;However, Cloudflare is a serverless provider, and serverless comes with its own challenges.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unlike Supabase, Cloudflare D1 is globally replicated; if we use the global replica, replication is asynchronous. When we write data, the replica may not yet be updated, leading to read latency.&lt;/li&gt;
&lt;li&gt;Our AI sessions need massive concurrent state updates, and D1 struggles to keep strong consistency under that load.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consequently a lot of hidden problems appear, especially when synchronising sessions and information, creating serious hazards. We tried storing session data as JSON (or other formats) in R2 with a write lock for consistency, but R2’s I/O cost is high and, being remote storage, it isn’t suitable for repeatedly reading, mutating, and writing back an entire session.&lt;/p&gt;
&lt;p&gt;That’s when I considered Cloudflare’s Durable Objects.&lt;/p&gt;
&lt;p&gt;DOs are described with a JavaScript class, have a globally unique instance identity, and a single instance per Durable Object ID runs at a time. Each instance owns private, transactional, strongly consistent persistent storage and provides a full SQLite storage API.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyDurableObject&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DurableObject&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Env&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SELECT &apos;Hello, World!&apos; as greeting&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;one&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;greeting&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; env&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Response&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stub &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;MY_DURABLE_OBJECT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;USER_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; stub&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can map a user or a session directly to a Durable Object, making that object the holder of its own state and the coordinator of all accesses to it.&lt;/p&gt;
&lt;p&gt;Durable Objects run in a serverless fashion, waking on demand, starting when they receive a request or an RPC call from another Worker, and being destroyed after a period of inactivity.&lt;/p&gt;
&lt;p&gt;They also implement Alarms, matching our future need for scheduled tasks.&lt;/p&gt;
&lt;p&gt;Durable Objects exist only on Cloudflare, so we deliberately gave up platform‑agnosticism and rebuilt the persistence layer in three tiers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tier 1 – D1 SQLite for users and authentication:&lt;/strong&gt; We use Better Auth and Drizzle.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tier 2 – Durable Objects for user data (memories, scheduled tasks, config):&lt;/strong&gt; stored in each User DO’s &lt;code&gt;ctx.storage.sql&lt;/code&gt;.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tier 3 – Durable Objects for session design:&lt;/strong&gt; built using the Cloudflare Agents SDK.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;           AI Chat
               │
      ┌────────┴────────┐
      │                 │
     D1                DO
      │                 │
     Auth        ┌──────┴──────┐
                 │             │
               User ──────&amp;gt; Sessions
                 │             │
              State A       State B&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Thus SQL handles relatively stable global data, while the strongly consistent, concurrent‑coordinated state is delegated to Durable Objects.&lt;/p&gt;
&lt;p&gt;For Luna specifically, two DOs each have a clear responsibility:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;UserDO&lt;/code&gt; holds per‑user memories, config, pet stats, skill lists, and file indexes.  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;ChatDO&lt;/code&gt; holds per‑session conversation history and model rounds.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A given user or session has only one instance running worldwide; write conflicts are coordinated by the DO singleton itself.&lt;/p&gt;
&lt;p&gt;When a Worker receives a user message, it awakens the corresponding &lt;code&gt;ChatAgent&lt;/code&gt;. The &lt;code&gt;ChatAgent&lt;/code&gt; pulls memory from &lt;code&gt;UserAgent&lt;/code&gt; to build the system prompt, streams the model, translates incremental model output into structured frames stored in Durable Objects, and finally streams the result back to the browser via SSE.&lt;/p&gt;
&lt;h3 id=&quot;moving-from-nuxt-to-tanstackstart-in-6hours-using-agents&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#moving-from-nuxt-to-tanstackstart-in-6hours-using-agents&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Moving from Nuxt to TanStack Start in 6 Hours Using Agents&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, we first built the project with OpenNext’s Next.js, then migrated to Vue’s Nuxt.js because we liked Nuxt Hub’s platform‑agnostic nature.&lt;/p&gt;
&lt;p&gt;When we settled on the DO architecture, we discovered that Next.js cannot properly bind Cloudflare Durable Objects—a known Cloudflare &lt;a href=&quot;https://developers.cloudflare.com/workers/wrangler/api/#supported-bindings&quot; rel=&quot;noopener noreferrer&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/cf-bindings-issue.png&quot; alt=&quot;Cloudflare DO binding not supported in Nuxt&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UserDO&lt;/code&gt; / &lt;code&gt;ChatDO&lt;/code&gt; cannot be accessed from Nuxt’s adapter layer, and without those bindings the project stalls.&lt;/p&gt;
&lt;p&gt;Thus we decided to move the entire codebase from Nuxt to TanStack Start using an Agent‑driven approach.&lt;/p&gt;
&lt;p&gt;The migration took about six hours, following a “divide‑and‑conquer, ReAct, and test” methodology.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1 – Clarify dependency mappings.&lt;/strong&gt;&lt;br /&gt;Nuxt is a convention‑heavy full‑stack framework with official modules for fonts, images, UI, content, animations, etc. We spun up several sub‑agents to find React equivalents—e.g., Nuxt Content → Content Collections, Nuxt Hub → Cloudflare Vite plugin, motion‑v → motion, TresJS → React Three Fiber, Nuxt UI → shadcn/ui. The React ecosystem is large enough that this step was smooth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/nuxt-to-tanstack-migration.png&quot; alt=&quot;Nuxt to TanStack Start migration flow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2 – Configure Skills and Hooks.&lt;/strong&gt;&lt;br /&gt;We used &lt;code&gt;Context7&lt;/code&gt; to fetch the latest docs of target libraries, then installed official Skills via &lt;code&gt;npx skills find&lt;/code&gt; and &lt;code&gt;bunx @tanstack/intent&lt;/code&gt;, preventing agents from calling incorrect APIs. ESLint, Prettier, and Git hooks were also set up to run type‑checking, linting, and formatting after each step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3 – Break down layers and define monorepo boundaries.&lt;/strong&gt;&lt;br /&gt;Nuxt’s &lt;code&gt;layers/&lt;/code&gt; (ordered by prefixes like &lt;code&gt;00.site&lt;/code&gt;, &lt;code&gt;01.auth&lt;/code&gt;, &lt;code&gt;06.chat&lt;/code&gt;, …) were split into &lt;code&gt;packages/@luna/*&lt;/code&gt;. &lt;code&gt;apps/web&lt;/code&gt; became the sole deployable TanStack Start app; the remaining packages are exported via sub‑paths with no cross‑layer reverse dependencies.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/monorepo-structure.png&quot; alt=&quot;Monorepo directory structure&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Explicit workspace dependencies replaced Nuxt’s auto‑discovery, making the structure clearer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4 – Switch UI system from Nuxt UI to shadcn.&lt;/strong&gt;&lt;br /&gt;We listed every component used, mapped them one‑by‑one to shadcn’s components, and placed them all in a &lt;code&gt;@luna/ui&lt;/code&gt; sub‑repo inside the monorepo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5 – Outline the main Agent with sub‑agent execution order and start implementation.&lt;/strong&gt;&lt;br /&gt;Because the layers are tightly intertwined, we had to plan the execution order of sub‑agents carefully—deciding which steps could run in parallel and which had to be sequential. Balancing speed and quality, we ordered the monorepo rebuilds to ensure a successful refactor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 6 – Launch the service and test with Chrome MCP.&lt;/strong&gt;&lt;br /&gt;We ran the dev server at &lt;code&gt;127.0.0.1:3000&lt;/code&gt;, used browser automation to walk through the full stack, and verified that DO RPC and SSE connections work correctly.&lt;/p&gt;
&lt;p&gt;We performed no manual codemods; everything was handled by AI using &lt;code&gt;Grok 4.6&lt;/code&gt;. After six hours we could run the complete conversation pipeline locally.&lt;/p&gt;
&lt;h3 id=&quot;threejs-and-vrm-pet&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#threejs-and-vrm-pet&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Three.js and VRM Pet&lt;/h3&gt;
&lt;p&gt;To give Luna a human‑like feel and a VR experience we planned to use models such as Live2D or 3D assets from the start.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/vrm-model.png&quot; alt=&quot;VRM model preview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The VRM model was crafted by our art lead in an afternoon: sculpting in VRoid Studio, adjusting bones, exporting a &lt;code&gt;.vrm&lt;/code&gt; file, and lighting it up in Three.js—about four hours total.&lt;/p&gt;
&lt;p&gt;VRM is essentially an extended glTF; &lt;code&gt;@pixiv/three-vrm&lt;/code&gt; handles humanoid bones, constraints, and blendshape expressions.&lt;/p&gt;
&lt;p&gt;Our approach separates &lt;strong&gt;rendering&lt;/strong&gt; from &lt;strong&gt;state&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rendering is an R3F scene that loads the VRM, computes a bounding‑box‑adaptive camera, and blends animation transitions.  &lt;/li&gt;
&lt;li&gt;State is a tiny state machine managing idle / happy / sleep actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For performance the Three.js scene only loads when the pet panel is expanded; when collapsed it releases everything except the stats panel.&lt;/p&gt;
&lt;p&gt;Pet stats (satiety, mood, intimacy) live inside &lt;code&gt;UserDO&lt;/code&gt; and are nudged after each conversation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/pet-status-panel.png&quot; alt=&quot;Pet status panel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We drive actions via the Dialogue Transport Protocol (described later) during chat sessions.&lt;/p&gt;
&lt;p&gt;When feeding, cleaning, etc., we also pre‑define some animations and maintain an internal state machine that occasionally triggers idle actions so the user never sees a static avatar.&lt;/p&gt;
&lt;h3 id=&quot;bash-tool-is-the-whole-harness&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#bash-tool-is-the-whole-harness&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bash Tool Is the Whole Harness&lt;/h3&gt;
&lt;p&gt;Next we designed the Harness. In the Agent Loop we followed the classic Pi pattern: store messages, estimate token usage, and compress once the budget is reached.&lt;/p&gt;
&lt;p&gt;The real challenge, however, was function calling.&lt;/p&gt;
&lt;p&gt;Each AI request has a time cost. If we turn every chat tool, pet tool, and other utility into separate tools, the number of API calls would explode, causing huge latency. Therefore we defined a lightweight protocol and three core tools.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;view:&lt;/strong&gt; the AI’s eyes; can retrieve large texts by line range and also supports multimodal media (images, videos, audio).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;patch:&lt;/strong&gt; the AI’s hands; can modify multiple files in one go.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bash:&lt;/strong&gt; the AI’s heart; all other functionalities are implemented through Bash.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;a-virtual-fs&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#a-virtual-fs&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A Virtual FS&lt;/h4&gt;
&lt;p&gt;We wanted Luna to have a “real‑computer‑like” file space so she could tackle complex tasks: directories, files, installable skills, and attachment storage. Cloudflare Workers lack a local FS, R2 is object storage, and DO SQLite isn’t suited for large blobs.&lt;/p&gt;
&lt;p&gt;Thus we built a Virtual File System (VFS) that separates &lt;em&gt;index&lt;/em&gt; from &lt;em&gt;content&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Index&lt;/strong&gt; – a &lt;code&gt;manifest&lt;/code&gt; stored in the user’s Durable SQLite, mapping each path to an object key, size, and type.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content&lt;/strong&gt; – immutable objects stored in R2; each write creates a new UUID key, and old objects are reclaimed by background jobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Permissions are layered:&lt;/p&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;/luna/system
    └── READ ONLY

/luna/user/&amp;lt;id&amp;gt;/
    ├── skills/       ← READ + WRITE
    └── attachments/  ← READ + WRITE

other paths
    └── DENY&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Concurrent updates use CAS (compare‑and‑swap): each commit includes an expected version number; conflicts return an error and trigger a retry, preventing index corruption.&lt;/p&gt;
&lt;p&gt;Quotas are explicit: max 5 MiB per file, up to 100 files, total 20 MiB.&lt;/p&gt;
&lt;h4 id=&quot;a-virtual-bash&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#a-virtual-bash&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A Virtual Bash&lt;/h4&gt;
&lt;p&gt;With the VFS in place, the next step was to give the Agent a powerful workhorse. &lt;code&gt;bash&lt;/code&gt; provides ~90 % of the capability. The AI already knows most Bash syntax from its training data, so we only need to expose a sandboxed Bash environment.&lt;/p&gt;
&lt;p&gt;We cannot hand the Agent a real Bash container for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cost:&lt;/strong&gt; spawning a container per conversation would be expensive.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; a real Bash environment would have uncontrolled permissions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Therefore we provide a virtual Bash using &lt;a href=&quot;https://justbash.dev&quot; rel=&quot;noopener noreferrer&quot;&gt;Just Bash&lt;/a&gt;, a Bash emulator that runs in a JS environment (our Cloudflare Workers). Its filesystem is the VFS described above, network access is limited to &lt;code&gt;curl&lt;/code&gt;, and execution quotas are enforced. Each conversation runs in an isolated shell that cannot affect other sessions.&lt;/p&gt;
&lt;p&gt;Because it’s virtual, we don’t need to preserve the tool as a standalone capability; instead we expose a custom factory function that lets us define commands.&lt;/p&gt;
&lt;p&gt;For example, we can create a &lt;code&gt;skill&lt;/code&gt; command that lets the agent search and read skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adding new functionality to the Agent is simply a matter of defining a custom command + associated skills.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Want Luna to remember something? Append a line to a file. Want to install a skill? Use &lt;code&gt;curl&lt;/code&gt; to fetch it into the skills directory. Want weather info, web search, Zhihu hot list? Register those as Bash custom commands. They can be invoked from the shell as CLI commands or called by the model as tools—one registry, two entry points.&lt;/p&gt;
&lt;p&gt;Some commands are &lt;em&gt;client&lt;/em&gt; commands that need browser cooperation (e.g., geolocation, notifications, theme switching). They travel via the &lt;code&gt;ChatDO&lt;/code&gt; WebSocket round‑trip to the current tab, creating a truly interactive “human‑AI romance” feeling.&lt;/p&gt;
&lt;p&gt;Thus our Harness is a filesystem + Bash; once the model learns to use Bash, it can manipulate Luna’s entire world.&lt;/p&gt;
&lt;h3 id=&quot;introducing-our-new-protocol-dialogue-transport-protocol&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#introducing-our-new-protocol-dialogue-transport-protocol&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Introducing Our New Protocol: Dialogue Transport Protocol&lt;/h3&gt;
&lt;p&gt;Every AI API round‑trip costs time and value, so we wanted the model to return multiple pieces of information at once.&lt;/p&gt;
&lt;p&gt;Our first design was JSONL, one JSON object per line:&lt;/p&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;{type: &quot;text&quot;, content: &quot;hello\nworld&quot;}
{type: &quot;sticker&quot;, id: &quot;smile&quot;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But we quickly ran into issues. Because the AI’s output is unstable, JSONL requires the model to escape every newline, which it rarely does reliably, making parsing fragile.&lt;/p&gt;
&lt;p&gt;Therefore we created a brand‑new protocol meeting several criteria:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No collision with common language syntax&lt;/strong&gt; – we can’t rely on plain JSON or XML because the model may output code that conflicts with those formats.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support multiple messages&lt;/strong&gt; – the AI should be able to emit several messages/events in one go, each with custom attributes (text, image, voice, video, reaction, vote, etc.).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Familiar yet simple&lt;/strong&gt; – low barrier, tolerant of errors (unclosed tags, mismatched quotes, nesting errors, duplicate nesting).  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to parse&lt;/strong&gt; – straightforward cross‑language implementation.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human‑readable&lt;/strong&gt; – the output is plain text for both AI and developers.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We therefore designed our own frame format, called &lt;strong&gt;Dialogue Transport Protocol (DTP)&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;┌──────────────────────────────────────────┐
│ &amp;lt;|message                                │
│   id=msg_123                             │  ← Header / Envelope
│   type=text                              │
│   reply=msg_100                          │
│   target=user_42                         │
│   ...                                    │
│ |&amp;gt;                                       │
├──────────────────────────────────────────┤
│                                          │
│  Hello!                                  │
│                                          │  ← Payload
│  This is **Markdown**.                   │
│                                          │
│  ```ts                                   │
│  console.log(&quot;hello&quot;)                    │
│  ```                                     │
│                                          │
├──────────────────────────────────────────┤
│ &amp;lt;|/message|&amp;gt;                             │  ← End
└──────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;what-the-protocol-looks-like&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#what-the-protocol-looks-like&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What the Protocol Looks Like&lt;/h4&gt;
&lt;p&gt;Each DTP frame is plain text; the delimiter &lt;code&gt;&amp;lt;| ... |&amp;gt;&lt;/code&gt; almost never collides with Markdown, code blocks, or HTML, and the model can generate it without escaping.&lt;/p&gt;
&lt;p&gt;The header uses HTML‑like key‑value pairs (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;reply&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt;, etc.) defined by the protocol; other fields can be added by specific message types.&lt;/p&gt;
&lt;p&gt;The payload is ordinary Markdown.&lt;/p&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;&amp;lt;|message id=a type=text |&amp;gt;
Hello
&amp;lt;|/message|&amp;gt;

&amp;lt;|message id=b type=sticker |&amp;gt;

&amp;lt;|message id=c type=reaction target=a emoji=❤️ |&amp;gt;

&amp;lt;|message id=d type=vote target=a options=&quot;A,B,C&quot; |&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&quot;streamed-parsing&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#streamed-parsing&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Streamed Parsing&lt;/h4&gt;
&lt;p&gt;Since the model streams token‑by‑token, DTP provides a streaming parser that consumes chunks as they arrive, performs implicit closing and reference resolution at the end, and shares the same lexer/parser as the batch mode.&lt;/p&gt;
&lt;p&gt;In Luna’s conversation pipeline the streaming parser sits between model output and frontend rendering: it translates the text stream into structured frames, stores them in DOs, pushes them via SSE, and dispatches events.&lt;/p&gt;
&lt;p&gt;Historical messages are deserialized back into DTP text and injected into the system prompt so the model can refer to existing IDs when replying.&lt;/p&gt;
&lt;p&gt;During generation, the streaming parser maps temporary IDs to stable server IDs and forwards the appropriate payload to the frontend. The process is completely transparent to the client; it receives typed messages one by one.&lt;/p&gt;
&lt;h2 id=&quot;epilogue&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#epilogue&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;Five days, three people, about 1,000 tokens, and we built our own OC catgirl—what a journey.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For strongly consistent sessions we gave up platform‑agnosticism and went all‑in on Cloudflare Durable Objects.  &lt;/li&gt;
&lt;li&gt;To make the Harness universally useful we stripped away a bunch of scattered tools and kept only Bash.  &lt;/li&gt;
&lt;li&gt;To satisfy our protocol needs we abandoned JSON and adopted the custom DTP message format.  &lt;/li&gt;
&lt;li&gt;To get the architecture running we left Nuxt’s comfort zone, using Agents to move the entire app in six hours.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These trade‑offs gave Luna her own filesystem, body, memory, skills, and communication protocol.&lt;/p&gt;
&lt;p&gt;If you also want to raise a Luna, feel free to chat with her at &lt;a href=&quot;https://luna.htu.me/chat&quot; rel=&quot;noopener noreferrer&quot;&gt;luna.htu.me/chat&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/assets/posts/luna/luna-chat-screenshot.jpg&quot; alt=&quot;Luna chat interface&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The source code is open‑sourced at &lt;a href=&quot;https://github.com/HackHTU/LUNA&quot; rel=&quot;noopener noreferrer&quot;&gt;LUNA&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Migrate to remix@3</title><link>https://me.htu.me/blog/%E8%BF%81%E7%A7%BB%E5%88%B0-remix-3?lang=en</link><guid isPermaLink="true">https://me.htu.me/blog/%E8%BF%81%E7%A7%BB%E5%88%B0-remix-3?lang=en</guid><pubDate>Wed, 29 Jul 2026 21:24:00 GMT</pubDate><description>&lt;p&gt;We migrated our blog to the beta Remix v3, which abandons React in favor of a TypeScript‑based UI model and an explicit “islands” architecture where only components marked with &lt;code&gt;clientEntry&lt;/code&gt; become client‑side JavaScript. By leveraging Remix’s built‑in TypeScript loader, a lazy asset server, Twind for on‑the‑fly Tailwind CSS, and Satori‑generated SVG/PNG captchas, we achieved a fully server‑rendered site with no build step, no default client scripts, and native Web‑API‑driven interactions. Although the ecosystem and documentation are still maturing, the migration demonstrates that Remix v3 can deliver type‑safe, componentized, and dynamically rich web applications while keeping the server, browser, and open standards at the core.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Recently, we migrated this blog to &lt;code&gt;remix@3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Remix v3 is not a routine upgrade from Remix v2; it drops React and builds a
full‑stack framework on its own. It is still in Beta, and both the API and
ecosystem are young.&lt;/p&gt;
&lt;p&gt;I decided to use Remix v3 at such an early stage because I really like it—it
returns to the essence of Web APIs.&lt;/p&gt;
&lt;h2 id=&quot;a-glimpse-of-remix-v3&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#a-glimpse-of-remix-v3&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A Glimpse of Remix v3&lt;/h2&gt;
&lt;p&gt;The most striking thing about Remix v3 is that it doesn’t use React; instead it
rewrites a TypeScript UI framework.&lt;/p&gt;
&lt;p&gt;Remix components are not React components, and there are no Hooks. A component
receives a &lt;code&gt;handle&lt;/code&gt; and then returns a true render function.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Greeting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Handle&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Hello, &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;State updates, events, server‑side rendering, and hydration all revolve around
this model. Pages render HTML on the server by default; only components
explicitly marked with &lt;code&gt;clientEntry()&lt;/code&gt; become JavaScript boundaries in the
browser. The server leaves markers for these boundaries and serializes their
props, and the browser’s &lt;code&gt;run()&lt;/code&gt; lazily loads the corresponding modules and
hydrates them. For example, a counter can be explicitly declared as a client
entry:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Counter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;clientEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;meta&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;handle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Handle&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; initialCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initialCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;
        &lt;span class=&quot;token attr-name&quot;&gt;mix&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;click&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          count&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here &lt;code&gt;import.meta.url&lt;/code&gt; is the server‑side source file path, e.g.
&lt;code&gt;file:///app/components/counter.tsx&lt;/code&gt;. When SSR encounters this component it
calls &lt;code&gt;resolveClientEntry()&lt;/code&gt;, passing the source path to the asset server, which
rewrites it to a URL the browser can fetch:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;renderToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;App&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveClientEntry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entryId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; component&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      href&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; assetServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getHref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entryId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      exportName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; component&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The server still outputs the button’s HTML first, but it marks the region of DOM
it owns with comments, roughly like this:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-html notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--rmx:h:counter-1--&amp;gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;0&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!--/rmx:h--&amp;gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;application/json&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;rmx-data&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token&quot;&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;c&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;counter-1&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;moduleUrl&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/assets/app/components/counter.tsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;exportName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Counter&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;props&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;initialCount&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The HTML above is simplified for protocol illustration, but the essential
structure comes from the Remix runtime: a pair of &lt;code&gt;rmx:h&lt;/code&gt; comments delimit the
hydration area, and &lt;code&gt;rmx-data&lt;/code&gt; stores the module URL, export name, and
serializable props. Even before any JavaScript is downloaded, the user can see
the server‑rendered button.&lt;/p&gt;
&lt;p&gt;Then the browser entry runs &lt;code&gt;run()&lt;/code&gt;. The runtime reads &lt;code&gt;rmx-data&lt;/code&gt;, walks the
comment markers, and hands each &lt;code&gt;moduleUrl + exportName&lt;/code&gt; to &lt;code&gt;loadModule()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadModule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;moduleUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exportName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; module &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;moduleUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; module&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;exportName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ready&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Only now does a real network request happen: when the browser executes
&lt;code&gt;import(&quot;/assets/app/components/counter.tsx&quot;)&lt;/code&gt;, the asset server reads the TSX
from disk, parses and transforms it with Oxc, and rewrites the file’s imports to
public URLs.&lt;/p&gt;
&lt;p&gt;The browser receives a standard ESM file; &lt;code&gt;loadModule()&lt;/code&gt; extracts the &lt;code&gt;Counter&lt;/code&gt;
export, the runtime reconstructs the virtual node with the serialized props,
hydrates the existing DOM between the two comments, and attaches the click
listener. Loading the same module again is cached, so ten &lt;code&gt;Counter&lt;/code&gt;s on the page
do not trigger ten requests.&lt;/p&gt;
&lt;p&gt;It may start to feel complicated, but this is a truly explicit Islands
architecture: instead of turning the whole component tree into a client‑side app
and then trying to trim JavaScript, there are no client components by
default—only the parts that truly need interaction become islands.&lt;/p&gt;
&lt;p&gt;Moreover, in the AI era, Remix writes everything. &lt;code&gt;remix/data-schema&lt;/code&gt; provides
Zod‑like, Valibot‑like schemas, checks, transformations, and &lt;code&gt;parseSafe()&lt;/code&gt;. It
also supports Standard Schema, which makes us admire Remix’s boldness.&lt;/p&gt;
&lt;p&gt;The simplest object validation looks like other schema libraries; the schema
itself describes both runtime constraints and output types:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; s &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;remix/data-schema&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; minLength &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;remix/data-schema/checks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; User &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;minLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  email&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  role&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defaulted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;reader&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Ada&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  email&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ada@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, it follows Standard Schema. &lt;code&gt;s.parse()&lt;/code&gt; and &lt;code&gt;s.parseSafe()&lt;/code&gt; accept not
only Remix’s own schemas but also Zod, Valibot, or ArkType schemas.&lt;/p&gt;
&lt;p&gt;Beyond that, Remix offers a suite of coordinated packages for routing,
middleware, sessions, authentication, and data tables. All capabilities are
imported from &lt;code&gt;remix/&amp;lt;subpath&amp;gt;&lt;/code&gt;, making the boundaries very clear.&lt;/p&gt;
&lt;h3 id=&quot;why-should-we-have-a-build-step&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#why-should-we-have-a-build-step&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why should we have a build step?&lt;/h3&gt;
&lt;p&gt;Another radical choice in Remix v3 is not treating a build step as a
prerequisite for running the app.&lt;/p&gt;
&lt;p&gt;Remix ships its own TypeScript/JSX loader that can be used in Node.js via
&lt;code&gt;--import&lt;/code&gt; flags.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-sh notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;--import&lt;/span&gt; remix/node-tsx ./server.ts&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;remix/node-tsx&lt;/code&gt; transforms &lt;code&gt;.ts&lt;/code&gt; and &lt;code&gt;.tsx&lt;/code&gt; files before Node executes them.&lt;/p&gt;
&lt;p&gt;Someone might ask, “what about the browser?” The browser obviously can’t run
TypeScript code, so Remix’s solution is clever.&lt;/p&gt;
&lt;p&gt;When client‑side JavaScript is needed, Remix’s asset server can serve modules
directly from source, rewrite imports, and generate preload links and
fingerprinted URLs. For example, you can map the source directory to a public
address under &lt;code&gt;/assets&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createAssetServer &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;remix/assets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; assetServer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createAssetServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  rootDir&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  basePath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/assets&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  fileMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;/app/*path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;app/*path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;/npm/*path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node_modules/*path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  allow&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;app/assets/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node_modules/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  deny&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;app/**/*.server.*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/assets/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; assetServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the browser requests a TSX entry, the asset server transpiles that module
and rewrites its imports to URLs the browser can fetch.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;allow&lt;/code&gt; and &lt;code&gt;deny&lt;/code&gt; together form a security boundary that prevents accidental
exposure of server‑only files. The server still compiles, but there is no need
to run a build ahead of time or generate and deploy a &lt;code&gt;dist&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Internally it composes Oxc parser, transformer, minifier, and resolver, then
uses &lt;code&gt;es-module-lexer&lt;/code&gt; to rewrite source, and Lightning CSS for styles.&lt;/p&gt;
&lt;p&gt;A pleasant surprise is that &lt;code&gt;fileMap&lt;/code&gt; can also bring &lt;code&gt;node_modules/&lt;/code&gt; into the
same dependency graph:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;fileMap&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/app/*path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;app/*path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;/npm/*path&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node_modules/*path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Suppose &lt;code&gt;Counter&lt;/code&gt; imports &lt;code&gt;on&lt;/code&gt; from &lt;code&gt;&quot;remix/ui&quot;&lt;/code&gt;. The asset server resolves that
bare specifier, finds the real package export inside &lt;code&gt;node_modules&lt;/code&gt;, and
rewrites it to something like &lt;code&gt;/assets/npm/remix/ui/index.js&lt;/code&gt;. When the browser
requests that URL, the asset server reads, processes, and returns it; its
dependencies are further resolved in the same way. The entire &lt;code&gt;node_modules&lt;/code&gt; is
not pre‑bundled but loaded on demand following the ESM dependency graph, just
like source files.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getPreloads()&lt;/code&gt; can walk this graph ahead of time and turn the modules that will
be requested into &lt;code&gt;&amp;lt;link rel=&quot;modulepreload&quot;&amp;gt;&lt;/code&gt; tags or &lt;code&gt;Link&lt;/code&gt; headers:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; preloads &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; assetServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPreloads&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;app/components/counter.tsx&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// /assets/app/components/counter.tsx&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// /assets/npm/remix/ui/index.js&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// …and the modules they depend on&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s the amazing part: it retains the browser’s native ESM request model while
still performing TypeScript transformation, package resolution, preload
generation, source‑map handling, minification, and fingerprinting—without
requiring us to emit a bundle first.&lt;/p&gt;
&lt;h2 id=&quot;preparations&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#preparations&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Preparations&lt;/h2&gt;
&lt;p&gt;In fact, I believe the native Web API is already powerful enough for us to rely
on it to implement everything. With that in mind, I set a few goals for the new
homepage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No client‑side JavaScript participation&lt;/strong&gt;&lt;br /&gt;The blog is inherently lightweight; every page can be static, with no client
JavaScript involved, at most using a form for comments, which can be solved
entirely with HTML.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No build process&lt;/strong&gt;&lt;br /&gt;Remix’s dev and start scripts do not involve a build step, so we don’t want to
add one either. I want to push directly to the remote repo and be able to run
immediately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement all blog functionality&lt;/strong&gt;&lt;br /&gt;A blog should have stats, comments, captchas, etc., a good UI/UX, a table of
contents, and nice interaction effects.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When an app no longer depends on a traditional bundler, tasks that were
previously handled automatically by the build system need to be redesigned. To
preserve Tailwind’s developer experience, streaming SSR, and dynamic images, we
performed a few extra steps.&lt;/p&gt;
&lt;h3 id=&quot;generating-tailwind-css-for-ssr-with-twind&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#generating-tailwind-css-for-ssr-with-twind&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Generating Tailwind CSS for SSR with Twind&lt;/h3&gt;
&lt;p&gt;This project uses Twind’s Tailwind preset rather than running the Tailwind CLI
to scan source files and produce a static CSS file. Twind can read the &lt;code&gt;class&lt;/code&gt;
attributes that appear in the final HTML and generate the necessary styles on
demand, which fits a server‑side app without a build step.&lt;/p&gt;
&lt;p&gt;The challenge is that Remix returns a stream, not a complete string. To handle
that we wrote a &lt;code&gt;twind()&lt;/code&gt; middleware:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;twind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Middleware &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;/* ... */&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pipeThrough&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TwindStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        signal&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;signal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It first waits for downstream SSR to finish, processes only &lt;code&gt;text/html&lt;/code&gt;
responses, and then pipes &lt;code&gt;response.body&lt;/code&gt; through a &lt;code&gt;TransformStream&lt;/code&gt;. The HTML
flowing through the stream is given to Twind’s &lt;code&gt;consume()&lt;/code&gt; for scanning; the CSS
generated from the first document fragment is injected into &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If a later Remix Frame stream emits new classes, the middleware compares the
previous and current style sets and writes only the incremental patch back to
the page. The key is keeping an independent Twind snapshot for each chunk of the
stream:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; restoreGlobalState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tw&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;restoreCurrentState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tw&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tw&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

restoreCurrentState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tw&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;restoreGlobalState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastStyle &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;/head&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;style data-twind&amp;gt;&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;${&lt;/span&gt;nextStyle&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/style&amp;gt;&amp;lt;/head&amp;gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; styleDiff &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getStyleDiff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastStyle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextStyle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;styleDiff&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createStylePatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;styleDiff&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Saving and restoring the global snapshot is crucial because Twind’s instance is
process‑wide, while SSR requests happen concurrently. Each response must see
only its own classes and must not mix rules generated for another request. The
first flush puts the complete stylesheet into &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;; subsequent flushes only
send insertion points and new CSS, which a tiny inline script patches into the
same &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;This does not break streaming rendering; the browser still receives the document
shell as early as possible, and later fragments receive the correct styles.&lt;/p&gt;
&lt;p&gt;Meanwhile we can continue using Tailwind‑style atomic classes, Typography,
Autoprefixer, theme tokens, and dark mode without any extra build commands.&lt;/p&gt;
&lt;h3 id=&quot;satorigenerated-captchas&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#satorigenerated-captchas&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Satori‑Generated Captchas&lt;/h3&gt;
&lt;p&gt;Satori is Vercel’s open‑source JSX/object‑to‑SVG renderer. It isn’t a browser
screenshot tool; it runs on the server, directly computing an SVG from layout
objects and font data. We then rasterize the SVG to PNG with Resvg.&lt;/p&gt;
&lt;p&gt;The most straightforward use case is dynamic Open Graph images. Our site first
validates the title length in the URL with &lt;code&gt;remix/data-schema&lt;/code&gt;, feeds both Latin
and Chinese fonts explicitly to Satori, and finally returns a PNG:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parsed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parseSafe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  ogImageQuerySchema&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token&quot;&gt;URL&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parsed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; parsed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; svg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;satori&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createCard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lang&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  width&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  height&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;630&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  fonts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;RENDER_FONT_DATA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instrumentSerif&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Instrument Serif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;RENDER_FONT_DATA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notoSerifSc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Noto Serif SC&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      weight&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; png &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Resvg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;svg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;asPng&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; png&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  png&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  png&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteOffset &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; png&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;byteLength&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ArrayBuffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;Cache-Control&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;public, max-age=3600, stale-while-revalidate=86400&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;image/png&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Vary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Accept-Language&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Simple, right? We’ve implemented OG images for the homepage and each blog post,
but the more interesting part is the comment captcha.&lt;/p&gt;
&lt;p&gt;The server randomly generates a set of shapes with different colors and chooses
one as the target. Satori and Resvg render the challenge as an image, while the
target position and a one‑time token are stored in Deno KV. The page uses native
HTML:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-html notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;captcha&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;...&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the user clicks the image, the browser submits &lt;code&gt;captcha.x&lt;/code&gt; and &lt;code&gt;captcha.y&lt;/code&gt;
together with the normal form—this is the behavior defined by the native
&lt;code&gt;&amp;lt;input type=&quot;image&quot;&amp;gt;&lt;/code&gt; element.&lt;/p&gt;
&lt;p&gt;The server only needs to validate the token and coordinates with
&lt;code&gt;remix/data-schema/form-data&lt;/code&gt;, then check whether the click landed near the
target shape. The challenge has a short TTL and is atomically deleted after
submission, so the same answer cannot be reused.&lt;/p&gt;
&lt;p&gt;What’s truly interesting isn’t the captcha algorithm but the implementation
approach: Satori generates the challenge, Deno KV holds short‑lived state, the
native image input collects click coordinates, and a regular HTTP form submits
everything. No event listeners, &lt;code&gt;fetch()&lt;/code&gt;, or any client‑side JavaScript are
involved.&lt;/p&gt;
&lt;h2 id=&quot;migration-work&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#migration-work&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Migration Work&lt;/h2&gt;
&lt;p&gt;With the goals defined, the implementation path was clear: use SSR everywhere.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-text notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;TypeScript / TSX + Markdown
        ↓
    runtime
        ↓
 Remix streaming SSR
        ↓
Twind injects CSS + Satori generates images
        ↓
   HTML / CSS / HTTP&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&quot;betting-on-ssr-rendering&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#betting-on-ssr-rendering&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Betting on SSR Rendering&lt;/h3&gt;
&lt;p&gt;Remix’s JSX runtime lives in &lt;code&gt;remix/ui&lt;/code&gt;, and it does a lot of heavy lifting.&lt;/p&gt;
&lt;p&gt;Routes must first be declared as a typed URL contract in &lt;code&gt;app/routes.ts&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; routes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  home&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  blog&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/blog&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    index&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    article&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/:slug&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    comment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/:slug/comments&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Controllers then return a standard &lt;code&gt;Response&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createController&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;routes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  actions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; get&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; render &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; i18n &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createI18n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LangContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Home&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;i18n&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;i18n&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;render()&lt;/code&gt; ultimately calls &lt;code&gt;renderToStream()&lt;/code&gt;, producing a
&lt;code&gt;ReadableStream&amp;lt;Uint8Array&amp;gt;&lt;/code&gt; that can be placed directly into a &lt;code&gt;Response&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;serve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;GET, POST, redirects, content‑negotiation, and error responses all happen within
a single HTTP pipeline.&lt;/p&gt;
&lt;p&gt;If comment form validation fails, the controller can re‑render the page with
error messages; on success it returns a redirect.&lt;/p&gt;
&lt;p&gt;There is no separate server‑side template plus a client‑side state layer—the
HTML itself is the final representation of state.&lt;/p&gt;
&lt;h3 id=&quot;injecting-new-css-capabilities&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#injecting-new-css-capabilities&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Injecting New CSS Capabilities&lt;/h3&gt;
&lt;p&gt;In recent years browsers have added many capabilities that previously required
JavaScript. The most useful for this project are cross‑document View Transitions
and Scroll‑driven Animations.&lt;/p&gt;
&lt;p&gt;Since we have no client JS, this must be an MPA, not an SPA.&lt;/p&gt;
&lt;p&gt;Page navigation remains ordinary &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; links with full document requests, but
with &lt;code&gt;@view-transition { navigation: auto; }&lt;/code&gt; the browser can provide View
Transitions across document navigations.&lt;/p&gt;
&lt;p&gt;For example, the blog list and article pages give their titles the same
&lt;code&gt;view-transition-name&lt;/code&gt;, so the two independent server responses can create a
seamless visual transition.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blogPostTitleTransitionName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slug&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDomId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blog-post-title&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slug&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    viewTransitionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;blogPostTitleTransitionName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slug&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; on the list page and the &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; on the article page share a stable
name. The browser builds a correspondence between the old and new titles across
the navigation, without any SPA router.&lt;/p&gt;
&lt;p&gt;We also implemented a scroll‑driven animation timeline for reading progress
inside articles.&lt;/p&gt;
&lt;p&gt;First we wrapped a simple &lt;code&gt;ScrollDrivenAnimation&lt;/code&gt; component:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ScrollDrivenAnimation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  handle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Handle&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ScrollDrivenAnimationProps&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    timeline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;view()&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    range &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;entry 0% cover 40%&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    keyframes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; animationStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    animationName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;scroll-&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;${&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    animationDuration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1ms&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    animationFillMode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;both&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    animationTimeline&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; timeline&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    animationRange&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; range&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@keyframes scroll-&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;${&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; keyframes&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;mix&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;animationStyle&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;children&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The article container declares a named view timeline, and each segment consumes
a portion of that timeline based on its percentage of the total text:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; articleTimelineStyle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  viewTimelineAxis&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;block&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  viewTimelineName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--article-reading&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScrollDrivenAnimation&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;--article-reading&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;cover 40% cover 60%&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token attr-name&quot;&gt;keyframes&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    from&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;var(--border)&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    to&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; color&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;var(--foreground)&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-timeline-line&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ScrollDrivenAnimation&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We calculate the number of words in each Markdown section; longer sections
occupy more of the timeline, and the heading marks also serve as ordinary anchor
links. Thus the heading is both a reading progress marker and a JavaScript‑free
TOC.&lt;/p&gt;
&lt;h3 id=&quot;i18n-a11y-and-others&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#i18n-a11y-and-others&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;i18n, a11y, and Others&lt;/h3&gt;
&lt;p&gt;Internationalization is also handled at the request boundary. The &lt;code&gt;locale()&lt;/code&gt;
middleware first reads &lt;code&gt;?lang=&lt;/code&gt; from the URL, falls back to parsing
&lt;code&gt;Accept-Language&lt;/code&gt; and its &lt;code&gt;q&lt;/code&gt; weights, then writes the language into a typed
&lt;code&gt;LangContext&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Middleware &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;LangContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getLangFromRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      property&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lang&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This value determines not only the body copy but also &lt;code&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt;, date
locale, OG locale, manifest URL, and the fonts used in dynamic OG images.&lt;/p&gt;
&lt;p&gt;The language information propagates down from a single request fact, so the
browser never needs to adjust the page after load—first‑paint HTML is complete
for search engines and screen readers.&lt;/p&gt;
&lt;p&gt;Regarding accessibility, using new Web capabilities does not mean ignoring
compatibility.&lt;/p&gt;
&lt;p&gt;View Transitions and scroll‑driven animations are progressive enhancements:
supported browsers get a smoother experience, while others retain normal
navigation, anchors, and content.&lt;/p&gt;
&lt;p&gt;At the same time, &lt;code&gt;prefers-reduced-motion&lt;/code&gt; can completely disable the
animations, letting system settings take precedence.&lt;/p&gt;
&lt;h2 id=&quot;deployment&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#deployment&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Deployment&lt;/h2&gt;
&lt;p&gt;Removing the build step yields a more direct development and deployment model,
but also makes platform differences unavoidable.&lt;/p&gt;
&lt;p&gt;We evaluated Cloudflare Workers, Vercel, and Deno.&lt;/p&gt;
&lt;h3 id=&quot;cloudflare-workers&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#cloudflare-workers&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cloudflare Workers&lt;/h3&gt;
&lt;p&gt;Workers are an excellent edge runtime, but they are not a traditional server
environment. Application code must be bundled into a module that Workers can
deploy, and the runtime provides no local filesystem that the app can read
arbitrarily.&lt;/p&gt;
&lt;p&gt;For many apps this limitation is fine, but for a blog it would require gathering
Markdown files, fonts, and other source assets into a bundle before deployment,
or moving them to external storage. We want the repository to be push‑ready and
to read Markdown directly from disk at runtime.&lt;/p&gt;
&lt;p&gt;Therefore Workers do not align with our core build‑less goal.&lt;/p&gt;
&lt;h3 id=&quot;vercel&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#vercel&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Vercel&lt;/h3&gt;
&lt;p&gt;Remix’s official build‑less path for Node relies on
&lt;code&gt;node --import remix/node-tsx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the Vercel serverless function entry we examined, there is no way to freely
add such a startup flag to the Node process, so we cannot run TS/TSX source
files directly as Remix recommends.&lt;/p&gt;
&lt;p&gt;We could add a build step using &lt;code&gt;esbuild&lt;/code&gt; to produce the expected output, but
that would re‑introduce the step we deliberately removed.&lt;/p&gt;
&lt;p&gt;Hence we did not choose Vercel.&lt;/p&gt;
&lt;h3 id=&quot;deno&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#deno&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Deno&lt;/h3&gt;
&lt;p&gt;Deno turned out to be the most natural answer. It natively executes TypeScript
and TSX without needing Node’s &lt;code&gt;--import&lt;/code&gt; loader.&lt;/p&gt;
&lt;p&gt;Modern Deno also supports npm packages, offering a much more mature Node
compatibility layer, plus WebAssembly, Deno FFI, and Node‑API &lt;code&gt;.node&lt;/code&gt; native
addons.&lt;/p&gt;
&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;code&gt;{
  &quot;tasks&quot;: {
    &quot;start&quot;: &quot;deno run --allow-env --allow-ffi --allow-read --allow-net deno.ts&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Thus the project can run Remix, Twind, Satori, and the Resvg native addon
together, use &lt;code&gt;Deno.serve()&lt;/code&gt; to handle requests, and store comments and one‑time
captchas in Deno KV.&lt;/p&gt;
&lt;p&gt;With the free tier sufficient for a personal site, Deno keeps the architecture
in its original shape.&lt;/p&gt;
&lt;p&gt;The source is the app—no generated directory, no required bundle to commit or
upload, and no extra tooling to adapt to a specific platform.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#conclusion&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Migrating to a beta framework certainly has costs.&lt;/p&gt;
&lt;p&gt;Documentation, API, ecosystem, and deployment experience are evolving rapidly;
we lack a mature ecosystem, so we have to read source code and write our own
middleware.&lt;/p&gt;
&lt;p&gt;But Remix 3 accounted for that. With AI gaining momentum, Remix 3’s libraries
are all TypeScript files, making Remix 3 agent‑friendly, unlike Next.js with its
myriad compiler tricks, especially directives like &lt;code&gt;use client&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Moreover, Remix does not implement its own PPR protocol; it builds directly on
Web APIs, achieving routing, server, components, data boundaries, and
progressive enhancement.&lt;/p&gt;
&lt;p&gt;You can use Islands, Frames, and client‑side hydration, or, like this site,
create zero client entries.&lt;/p&gt;
&lt;p&gt;Remix v3 is still very new, and we are continuing to watch its development, but
it demonstrates that modern web applications can have type safety,
componentization, dynamic images, and interaction while keeping the server,
browser, and open standards at the core.&lt;/p&gt;
</description></item><item><title>How to Build a Serverless Agent</title><link>https://me.htu.me/blog/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E4%B8%80%E4%B8%AA%E6%97%A0%E6%9C%8D%E5%8A%A1%E5%99%A8-agent?lang=en</link><guid isPermaLink="true">https://me.htu.me/blog/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E4%B8%80%E4%B8%AA%E6%97%A0%E6%9C%8D%E5%8A%A1%E5%99%A8-agent?lang=en</guid><pubDate>Mon, 27 Jul 2026 03:25:00 GMT</pubDate><description>&lt;p&gt;Serverless platforms are a natural fit for AI agents because they only need to run when a request arrives, allowing cheap, always‑on‑free deployments; Vercel Workflow provides the long‑running, stateful orchestration required for such on‑demand agents. By leveraging Workflow’s &lt;code&gt;use workflow&lt;/code&gt;/&lt;code&gt;use step&lt;/code&gt; directives, hooks for Human‑in‑the‑Loop interactions, and the Vercel AI SDK, ClawLess demonstrates how to build a durable, interactive agent that handles messages, tool approvals, delayed tasks, and context management (compression and pruning) without a resident process. The article concludes that assembling these components makes creating functional, serverless agents straightforward and invites developers to extend the approach with custom tools and sandboxed execution.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In early April, we open‑sourced the Serverless project
&lt;a href=&quot;https://github.com/Niapya/clawless&quot; rel=&quot;noopener noreferrer&quot;&gt;ClawLess&lt;/a&gt;, which can deploy a lightweight
Agent that is available 24 hours for free on your Vercel account.&lt;/p&gt;
&lt;p&gt;This article records the trade‑offs behind ClawLess: why it should be
Serverless, and how we assembled a working Agent using Vercel Workflow, Vercel
AI SDK, Vercel Sandbox, KV, Blob, and Postgres Vector.&lt;/p&gt;
&lt;h2 id=&quot;motivation&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#motivation&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Motivation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Building a complete, long‑running Agent runtime costs not the model, but the
always‑on part.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can pick a cheap or free model, but you still need your own server, VPS, or
a constantly‑online Mac mini.&lt;/p&gt;
&lt;p&gt;Some projects have offered a different solution: rewrite the runtime in a
smaller native language, push memory usage down, and run it on a Raspberry Pi,
development board, or another cheap Linux device.&lt;/p&gt;
&lt;p&gt;Those solutions still need a device that stays online to run the Agent, maintain
its state, wait for user messages, and handle scheduled jobs, and ClawLess was
never intended for that approach.&lt;/p&gt;
&lt;p&gt;For most people, the most common entry point for an Agent is still a chatbot:
the user sends a message, the Agent reads context, calls tools, and returns a
result. It does not need to run on the CPU every second; it only needs to
reliably handle a request when it is woken up.&lt;/p&gt;
&lt;p&gt;We only need to process a request when a conversation arrives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If it’s on the web, that’s an API request triggered when the user sends a
message.&lt;/li&gt;
&lt;li&gt;If it’s connected to your IM, that’s a webhook triggered when the IM receives
a request (or it could be an API request).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition, we need delayed and scheduled tasks, e.g. “remind me tomorrow to
continue this topic” or “every morning summarize a channel”. These tasks don’t
require a resident process; they only need the platform to pause a workflow,
wait, and resume at the appropriate time.&lt;/p&gt;
&lt;p&gt;Old friends know I’m a fan of Serverless. Its core idea is: start when a request
arrives, sleep while a task is pending, and wake up again when the time comes.&lt;/p&gt;
&lt;p&gt;Because of this characteristic, Serverless platforms can be deployed at very low
cost, especially for JS runtimes such as AWS Lambda, Vercel, Cloudflare Workers,
and many of them have generous free tiers.&lt;/p&gt;
&lt;p&gt;After some research, I settled on Vercel Workflow. It can start long‑running
tasks, wait inside a workflow, and resume later, so ClawLess can be woken by
messages, webhooks, tool approvals, and schedules.&lt;/p&gt;
&lt;p&gt;In other words, ClawLess does &lt;strong&gt;not&lt;/strong&gt; need to run continuously; it only needs to
be invoked on demand. That’s exactly what a Workflow‑style runtime excels at.&lt;/p&gt;
&lt;h2 id=&quot;design&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#design&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Design&lt;/h2&gt;
&lt;p&gt;Since it has both front‑end and back‑end, this is a full‑stack project.&lt;/p&gt;
&lt;p&gt;For full‑stack we have tried Nuxt or Hono‑SSR approaches, but we chose Next.js
as the framework because we need to use the Vercel Workflow API to build the
Agent, and Next.js is the native framework for Vercel Workflow.&lt;/p&gt;
&lt;h2 id=&quot;workflow-as-the-agent-runtime&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#workflow-as-the-agent-runtime&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Workflow as the Agent Runtime&lt;/h2&gt;
&lt;p&gt;First, a quick intro to Vercel Workflow, Vercel’s newly released official,
long‑running, stateful process framework. In our tests a workflow can run for at
least 10 minutes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When we built ClawLess, Vercel Workflow was still in beta, but the API is now
stable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It uses &lt;code&gt;&quot;use workflow&quot;;&lt;/code&gt; and &lt;code&gt;&quot;use step&quot;;&lt;/code&gt; directives to define a workflow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;use workflow&lt;/code&gt; handles &lt;strong&gt;orchestration&lt;/strong&gt; – cross‑step state, waiting,
resuming, and the overall flow.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use step&lt;/code&gt; handles &lt;strong&gt;execution&lt;/strong&gt; – individual retryable, side‑effect‑heavy
atomic operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For an Agent, the logic that actually talks to a database, third‑party API, or
Sandbox command usually lives in a &lt;code&gt;step&lt;/code&gt;; waiting for a user message, waiting
for approval, or waiting until 9 am tomorrow belongs in the &lt;code&gt;workflow&lt;/code&gt; layer.&lt;/p&gt;
&lt;p&gt;An example workflow:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;use step&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; userId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;userOnboardingWorkflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;use workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For AI, the most popular TypeScript framework is the Vercel AI SDK.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; convertToModelMessages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; streamText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UIMessage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messages &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UIMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;streamText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    model&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anthropic/claude-sonnet-4.5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    system&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are a helpful assistant.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;convertToModelMessages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toUIMessageStreamResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On the front‑end you can use the
&lt;a href=&quot;https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;useChat&lt;/code&gt;&lt;/a&gt; hook to easily
build a chat UI.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;use client&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useChat &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@ai-sdk/react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; DefaultChatTransport &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sendMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useChat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    transport&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DefaultChatTransport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      api&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/api/chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setInput&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;div key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;User: &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AI: &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;part&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt;
            part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;span key&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;index&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;part&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;span&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;div&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;form
        onSubmit&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;sendMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; input &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;setInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;input
          value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          onChange&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          disabled&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ready&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          placeholder&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Say something...&quot;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;button type&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;submit&quot;&lt;/span&gt; disabled&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ready&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
          Submit
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;button&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;form&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;useChat&lt;/code&gt; hooks also have Vue, Svelte, and Angular libraries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That gives a perfectly usable Web Chat, but it’s not yet a &lt;strong&gt;Durable Agent&lt;/strong&gt;
because the runtime ends when the request finishes.&lt;/p&gt;
&lt;p&gt;Our Agent needs to keep running after a request ends and persist its state.&lt;/p&gt;
&lt;p&gt;So our workflow is equipped with an AI module that is deeply integrated with the
AI SDK and provides a &lt;code&gt;DurableAgent&lt;/code&gt; suited for workflow scenarios.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Workflow.getWritable()&lt;/code&gt; returns a persistent writable stream; the client can
disconnect and later reconnect to receive the rest of the output.&lt;/p&gt;
&lt;p&gt;A simplified illustration:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; DurableAgent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@workflow/ai/agent&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getWritable &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; UIMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; UIMessageChunk &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chatWorkflow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UIMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;use workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token function&quot;&gt;getWritable&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UIMessageChunk&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; agent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DurableAgent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    model&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;anthropic/claude-sonnet-4.5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    system&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;You are a helpful assistant.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    messages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    writable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the Agent truly behaves like an autonomous system.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In ClawLess the process is more involved: the main workflow reads the current
configuration from KV, builds a system prompt, loads tools from MCP, creates a
&lt;code&gt;DurableAgent&lt;/code&gt;, writes output to the UI message stream (and persists it), and
finally saves state to Postgres SQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;hook-makes-the-workflow-truly-interactive&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#hook-makes-the-workflow-truly-interactive&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hook Makes the Workflow Truly Interactive&lt;/h2&gt;
&lt;p&gt;Consider the case where a user continues to send messages on the web, or an IM
webhook delivers more messages while the workflow is still running. We should
&lt;strong&gt;follow‑up&lt;/strong&gt; with the existing Agent instead of spawning a brand‑new workflow
each time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the basic logic of Human‑in‑the‑Loop (HITL). For more details see
Wikipedia’s
&lt;a href=&quot;https://en.wikipedia.org/wiki/Human-in-the-loop&quot; rel=&quot;noopener noreferrer&quot;&gt;Human‑in‑the‑Loop&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fortunately, Workflow provides &lt;strong&gt;hooks&lt;/strong&gt; that enable this pattern. A hook lets a
running workflow pause at a certain point, wait for an external event, and then
resume.&lt;/p&gt;
&lt;p&gt;The simplest external event is “the next user message”:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; defineHook &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Define a hook that receives an external event (the next user message)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; messageHook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;&lt;span class=&quot;token function&quot;&gt;defineHook&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;conversation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;use workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; events &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; messageHook&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    token&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chat-&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Wait for external events (the next user message)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Received&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;from&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; messageHook&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;chat-&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same mechanism works well for tool approvals. For example, when the model
wants to execute a high‑risk command—delete a file, send a message, place an
order, transfer money—we should not run it directly. Instead we throw an
approval request to the front‑end and wait for user confirmation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;See the AI SDK documentation on
&lt;a href=&quot;https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval&quot; rel=&quot;noopener noreferrer&quot;&gt;Tool Execution Approval&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using workflow hooks means you don’t have to poll a database or write a complex
state machine; the workflow suspends while waiting and resumes from the same
state.&lt;/p&gt;
&lt;p&gt;So the typical Agent workflow looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user message hits the entry point.&lt;/li&gt;
&lt;li&gt;The entry point checks whether the current session already has an active
workflow.&lt;/li&gt;
&lt;li&gt;If it does, the new message is sent back via a hook.&lt;/li&gt;
&lt;li&gt;If not, a new workflow is created from scratch.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;context&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#context&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Context&lt;/h2&gt;
&lt;p&gt;Agents have a &lt;strong&gt;context limit&lt;/strong&gt;. Token‑counting differs between models, but they
all share one constraint: you must fit the system prompt, history, tool results,
file inputs, and memory into the context window.&lt;/p&gt;
&lt;p&gt;Moreover, as the context grows, model accuracy degrades and costs rise.&lt;/p&gt;
&lt;p&gt;Two common approaches for handling long context are &lt;strong&gt;compression&lt;/strong&gt; and
&lt;strong&gt;pruning&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;compression&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#compression&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Compression&lt;/h3&gt;
&lt;p&gt;Compression rewrites “very long but still needed” history into a shorter
representation. A typical pattern is: once a conversation reaches a certain
length, ask the model to summarize early dialogue, key tool results, and
confirmed user preferences, then remove the original messages from the current
window.&lt;/p&gt;
&lt;p&gt;On the next real model call you prepend the summary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want more control, you can use a sliding‑window approach that includes
the summary plus the most recent rounds and the current input.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; generateText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModelMessage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compactMessages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ModelMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; summary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    model&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;messages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        role&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&quot;Please summarize our previous conversation and extract the key information that will help later.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    recentMessages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In a Workflow this is natural because the workflow already has its own lifecycle
and external storage. After a round finishes you can write the summary to KV,
delete the old messages, and keep only a “session summary” field. The durable
session persists, but the context fed to the model does not grow indefinitely.&lt;/p&gt;
&lt;h3 id=&quot;pruning&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#pruning&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pruning&lt;/h3&gt;
&lt;p&gt;Pruning does not try to understand the history; it simply cuts away parts that
are unlikely to help the current round.&lt;/p&gt;
&lt;p&gt;The AI SDK already provides &lt;code&gt;pruneMessages&lt;/code&gt;, which can strip old reasoning, tool
call results, approval traces, and empty messages before sending the payload to
the model.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ModelMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pruneMessages &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildContextWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ModelMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pruned &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pruneMessages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    messages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    reasoning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;before-last-message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    toolCalls&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;before-last-2-messages&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    emptyMessages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;remove&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pinned &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pruned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; recent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pruned&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;pinned&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;recent&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For Workflow‑based AI you typically call &lt;code&gt;prepareStep&lt;/code&gt; before each model
invocation to rewrite the window.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  messages&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  writable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// prepareStep runs before each model call to reshape the context window&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;prepareStep&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentMessages &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildContextWindow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentMessages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compression and pruning are not mutually exclusive; most production systems
combine them according to design needs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the idea of &lt;strong&gt;Context Engineering&lt;/strong&gt; – see Anthropic’s article
&lt;a href=&quot;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&quot; rel=&quot;noopener noreferrer&quot;&gt;Effective Context Engineering for AI Agents&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Beyond that, we also need cross‑conversation, cross‑knowledge, or permanent
shared context. For those cases we store data in external systems via tools
rather than stuffing everything into the model’s context.&lt;/p&gt;
&lt;h2 id=&quot;tools&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#tools&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tools&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;An Agent’s tools define its capability boundary.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Without tools it is just a chat model; with tools it can store memory, execute
commands, learn skills, schedule tasks, or delegate work to sub‑Agents.&lt;/p&gt;
&lt;p&gt;In the AI SDK, tools are first‑class citizens. A minimal tool definition looks
like this:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; tool &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; z &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;zod&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tools &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  remember&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Save a useful fact into long‑term memory&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    inputSchema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      note&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; note &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; memory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildMemory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;note&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ok&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; memory &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&quot;memory&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#memory&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Memory&lt;/h3&gt;
&lt;p&gt;Memory stores long‑term facts and user preferences.&lt;/p&gt;
&lt;p&gt;You can keep it in KV, a database, or as files in Blob, and expose tools for the
model to manage it.&lt;/p&gt;
&lt;p&gt;For search, full‑text keyword search is convenient but has limits; two sentences
may be highly related without sharing any words.&lt;/p&gt;
&lt;p&gt;Consider &lt;strong&gt;RAG (Retrieval‑Augmented Generation)&lt;/strong&gt; – letting the model retrieve
relevant information from an external knowledge base while generating a
response.&lt;/p&gt;
&lt;p&gt;The simplest approach is vector‑embedding search: encode a sentence into a
multi‑dimensional vector and find the most similar vectors in the space. Cosine
similarity is a common metric:&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;Similarity&lt;/mi&gt;&lt;mo&gt;⁡&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mover&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mo&gt;^&lt;/mo&gt;&lt;/mover&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mover&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mo&gt;^&lt;/mo&gt;&lt;/mover&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo&gt;∑&lt;/mo&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;msub&gt;&lt;mover&gt;&lt;mi&gt;q&lt;/mi&gt;&lt;mo&gt;^&lt;/mo&gt;&lt;/mover&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;msub&gt;&lt;mover&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mo&gt;^&lt;/mo&gt;&lt;/mover&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\operatorname{Similarity} = \hat{\mathbf{q}} \cdot \hat{\mathbf{d}} = \sum_{i=1}^{n} \hat q_i \hat d_i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.8889em;vertical-align:-0.1944em&quot;&gt;&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;mathrm&quot; style=&quot;margin-right:0.0139em&quot;&gt;Similarity&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2778em&quot;&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2778em&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.9023em;vertical-align:-0.1944em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.7079em&quot;&gt;&lt;span style=&quot;top:-3em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mathbf&quot;&gt;q&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.0134em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.25em&quot;&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.1944em&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222em&quot;&gt;&lt;/span&gt;&lt;span&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222em&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.9579em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9579em&quot;&gt;&lt;span style=&quot;top:-3em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mathbf&quot;&gt;d&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.2634em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.25em&quot;&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2778em&quot;&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2778em&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:2.9291em;vertical-align:-1.2777em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;op-limits&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.6514em&quot;&gt;&lt;span style=&quot;top:-1.8723em;margin-left:0em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.05em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3&quot;&gt;&lt;span&gt;&lt;span class=&quot;mathnormal&quot;&gt;i&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.05em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.05em&quot;&gt;&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;op-symbol large-op&quot;&gt;∑&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.3em;margin-left:0em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.05em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3&quot;&gt;&lt;span&gt;&lt;span class=&quot;mathnormal&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.2777em&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.1667em&quot;&gt;&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.6944em&quot;&gt;&lt;span style=&quot;top:-3em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mathnormal&quot; style=&quot;margin-right:0.0359em&quot;&gt;q&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.1667em&quot;&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.1944em&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.3117em&quot;&gt;&lt;span style=&quot;top:-2.55em;margin-left:-0.0359em;margin-right:0.05em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3&quot;&gt;&lt;span class=&quot;mathnormal&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class=&quot;accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9579em&quot;&gt;&lt;span style=&quot;top:-3em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mathnormal&quot;&gt;d&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.2634em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.0833em&quot;&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.3117em&quot;&gt;&lt;span style=&quot;top:-2.55em;margin-left:0em;margin-right:0.05em&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3&quot;&gt;&lt;span class=&quot;mathnormal&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Normalize the similarity scores and sort.&lt;/p&gt;
&lt;p&gt;In production we usually combine vector search with keyword search (hybrid
search).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a deeper dive into Memory, see
&lt;a href=&quot;https://docs.openclaw.ai/concepts/memory&quot; rel=&quot;noopener noreferrer&quot;&gt;OpenClaw’s Memory design&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;skills&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#skills&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Skills&lt;/h3&gt;
&lt;p&gt;Skills are more like file‑system‑based knowledge bases.&lt;/p&gt;
&lt;p&gt;A Skill typically lives in a directory structure such as:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-bash notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;my-skill $ tree

my-skill/
    SKILL.md

    scripts/
    templates/
    examples/
    assets/&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Skills describe &lt;strong&gt;how&lt;/strong&gt; to do things, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to write a certain type of test&lt;/li&gt;
&lt;li&gt;How to invoke an internal specification&lt;/li&gt;
&lt;li&gt;How to generate code according to team conventions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We usually extract a Skill’s title and description and inject them into the
System Prompt.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Skills were introduced by Anthropic; see
&lt;a href=&quot;https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview&quot; rel=&quot;noopener noreferrer&quot;&gt;Agent Skills Overview&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;execute-bash&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#execute-bash&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Execute Bash&lt;/h3&gt;
&lt;p&gt;Execution tools let the Agent actually interact with the outside world.&lt;/p&gt;
&lt;p&gt;The most basic execution tool is a Bash tool that runs shell commands, bridging
the Agent to a real environment.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In ClawLess we use &lt;code&gt;@vercel/sandbox&lt;/code&gt; to implement the Bash tool.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bash is the highest‑risk tool and requires strong isolation, careful approval,
and sandboxing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you’re looking for a Serverless Bash tool or a sandboxed Bash
implementation, check out our &lt;a href=&quot;https://github.com/Niapya/ai-sdk-x&quot; rel=&quot;noopener noreferrer&quot;&gt;AI SDK X&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;tasks&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#tasks&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tasks&lt;/h3&gt;
&lt;p&gt;When handling complex work we need to consider more categories.&lt;/p&gt;
&lt;h4 id=&quot;delayed-and-scheduled-tasks&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#delayed-and-scheduled-tasks&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Delayed and Scheduled Tasks&lt;/h4&gt;
&lt;p&gt;A delayed task notifies you after a certain amount of time.&lt;/p&gt;
&lt;p&gt;ClawLess implements delayed tasks inside a Workflow using the &lt;code&gt;sleep&lt;/code&gt; function.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sleep &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;remindTomorrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;use workflow&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1 day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sendReminder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A scheduled task (Cron Job) runs at a specific time repeatedly.&lt;/p&gt;
&lt;p&gt;Implementation is similar; we use an infinite loop with &lt;code&gt;sleep&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;1 day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;runTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&quot;subagents&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#subagents&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sub‑Agents&lt;/h4&gt;
&lt;p&gt;A sub‑task does not have to be a full‑blown Agent; it can delegate a small piece
of work.&lt;/p&gt;
&lt;p&gt;For example, the main Agent handles the conversation, but a specific research
question could be handed to a cheaper, smaller model.&lt;/p&gt;
&lt;p&gt;A minimal Sub‑Agent implementation:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; generateText&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tool &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ai&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; z &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;zod&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; delegateResearch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hand off a focused research subtask&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  inputSchema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    question&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; z&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; question &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      model&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;openai/gpt-4.1-mini&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      prompt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Answer briefly and return only key facts:\n&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; question&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;text &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&quot;mcp-and-other-tools&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#mcp-and-other-tools&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MCP and Other Tools&lt;/h3&gt;
&lt;p&gt;The Model Context Protocol (MCP) is a unified protocol that lets an Agent
connect to external systems in a standardized way.&lt;/p&gt;
&lt;p&gt;If you are building a domain‑specific Agent, MCP is often a better fit than a
handful of ad‑hoc Skills.&lt;/p&gt;
&lt;p&gt;ClawLess does not implement web search, browser navigation, etc., directly;
instead we can plug those capabilities in via MCP.&lt;/p&gt;
&lt;h2 id=&quot;connecting-to-the-frontend&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#connecting-to-the-frontend&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connecting to the Frontend&lt;/h2&gt;
&lt;p&gt;The front‑end that interacts with the Agent can be a web page, a Bot, or any
other client.&lt;/p&gt;
&lt;h3 id=&quot;web-chat&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#web-chat&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Web Chat&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;useChat&lt;/code&gt; hook we showed earlier is the simplest web‑chat solution; we won’t
repeat it here.&lt;/p&gt;
&lt;h3 id=&quot;connecting-to-im-via-webhook&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#connecting-to-im-via-webhook&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connecting to IM via Webhook&lt;/h3&gt;
&lt;p&gt;Most instant‑messaging platforms expose Bot capabilities (e.g., Telegram,
Discord).&lt;/p&gt;
&lt;p&gt;Bots typically receive messages either via &lt;strong&gt;long polling&lt;/strong&gt; or &lt;strong&gt;webhooks&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Long polling means the Bot periodically (the “heartbeat”) asks the server for
new messages; OpenClaw’s connectors work this way.&lt;/p&gt;
&lt;p&gt;Webhooks are more efficient: the server pushes a new‑message notification to the
Bot without the Bot having to request.&lt;/p&gt;
&lt;p&gt;ClawLess uses the &lt;a href=&quot;https://chat-sdk.dev&quot; rel=&quot;noopener noreferrer&quot;&gt;Chat SDK&lt;/a&gt; to connect to various IMs via
webhook.&lt;/p&gt;
&lt;p&gt;The Chat SDK is straightforward: create a &lt;code&gt;Chat&lt;/code&gt; instance.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Next: lib/bot.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Chat &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chat&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bot &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Chat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  userName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mybot&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  adapters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; slack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRedisState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

bot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onNewMention&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;thread&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; agent&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then expose an API route that handles the webhook.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Next: app/api/webhooks/slack/route.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;POST&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; bot&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;webhooks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slack&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#conclusion&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By now you should see that building an Agent can be surprisingly simple.&lt;/p&gt;
&lt;p&gt;We hope you combine these pieces to create your own Agents—it’s genuinely fun.&lt;/p&gt;
&lt;p&gt;We are also building a universal sandboxed Bash that works in any JS Runtime,
Serverless or embedded environments. If you’re interested, check out our
&lt;a href=&quot;https://niapya.github.io/ai-sdk-x/&quot; rel=&quot;noopener noreferrer&quot;&gt;AI‑SDK‑X documentation&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Welcome To Niapya&apos;s Blog</title><link>https://me.htu.me/blog/welcome-to-niapyas-blog?lang=en</link><guid isPermaLink="true">https://me.htu.me/blog/welcome-to-niapyas-blog?lang=en</guid><pubDate>Mon, 27 Jul 2026 03:25:00 GMT</pubDate><description>&lt;p&gt;The author argues that, despite the dominance of AI‑generated “Vibe‑coded” blogs, a personal blog should remain fully under the author’s control, so they built a lightweight static‑site generator using Hono/JSX for templating, marked for Markdown, and UnoCSS for atomic styling, resulting in a site with zero client‑side JavaScript. They demonstrate a working prototype and outline future plans to evolve it into a full framework with plugins, a retro UI component library, and optional SSR, all open‑sourced on GitHub.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Yes, I finally have a blog.&lt;/p&gt;
&lt;p&gt;2026 is the year &lt;strong&gt;Vibe Coding&lt;/strong&gt; exploded. We&apos;ve gotten used to using &lt;strong&gt;Code
Agent&lt;/strong&gt; to write any project, and the old “hand‑coded” approach is mocked as
“ancient programming”. By now most people have lost the habit of writing code
themselves; if they want a blog, they just &lt;strong&gt;Vibe&lt;/strong&gt; a &lt;code&gt;Next.js&lt;/code&gt; template, or
even use an off‑the‑shelf &lt;strong&gt;SaaS&lt;/strong&gt; blogging platform with &lt;code&gt;Skills&lt;/code&gt; and let an
Agent write the posts.&lt;/p&gt;
&lt;p&gt;But I think a blog, as a personal platform, should be fully controlled by the
author. So I want to build my own blogging framework, my own blog style, and my
own blog content from scratch.&lt;/p&gt;
&lt;h2 id=&quot;building-the-blog-framework&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#building-the-blog-framework&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Building the Blog Framework&lt;/h2&gt;
&lt;p&gt;Since sites of this type usually don’t need server‑side logic, we call them
&lt;strong&gt;static sites&lt;/strong&gt;. What I need to create is a
&lt;strong&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/SSG&quot; rel=&quot;noopener noreferrer&quot;&gt;Static Site Generator&lt;/a&gt;&lt;/strong&gt;
(SSG).&lt;/p&gt;
&lt;p&gt;The SSG workflow is simple: fetch a content source and parse it into documents,
render them with templates, and output the result as website files. I want to
control every step.&lt;/p&gt;
&lt;h3 id=&quot;step-1--fetch-the-content-source-and-parse-it-into-documents&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#step-1--fetch-the-content-source-and-parse-it-into-documents&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Step 1 – Fetch the content source and parse it into documents&lt;/h3&gt;
&lt;p&gt;The source can be anything: a database, a CMS, Markdown files, etc. To unify the
process we define an interface like this:&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;definePost&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    title&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    category&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    tags&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    content&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Post&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can define our content anywhere, in any way. In the future plugins could
pull content from file routes, databases, or even the browser.&lt;/p&gt;
&lt;p&gt;Blog content (&lt;code&gt;content&lt;/code&gt;) is usually written in Markdown, so I use &lt;strong&gt;marked&lt;/strong&gt; to
compile Markdown to HTML, together with &lt;strong&gt;marked‑highlight&lt;/strong&gt; and
&lt;strong&gt;highlight.js&lt;/strong&gt; for code highlighting.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; hljs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;highlight.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Marked &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;marked&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; markedHighlight &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;marked-highlight&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; markdownRenderer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Marked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  breaks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  gfm&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

markdownRenderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;markedHighlight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    emptyLangClass&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hljs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    langPrefix&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hljs language-&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;highlight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; language&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; highlightLanguage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hljs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getLanguage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;language&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; language
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;plaintext&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; hljs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;highlight&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; language&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; highlightLanguage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; markdownRenderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdown&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; renderer &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&quot;step-2--render-with-templates&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#step-2--render-with-templates&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Step 2 – Render with templates&lt;/h3&gt;
&lt;p&gt;Because we are producing website files, most SSGs tie the template closely to
the HTML structure. If you pick a custom template language you have to define an
AST and a compiler yourself—for example, Astro’s &lt;code&gt;.astro&lt;/code&gt; files are a
proprietary component syntax. You’d also need to build a toolchain for type
checking and syntax highlighting, which hurts developer experience.&lt;/p&gt;
&lt;p&gt;Some SSGs use React/Vue components as templates. Those feel nice to develop
with, but they tend to be tightly coupled to the respective framework’s
ecosystem and can unintentionally pull in client‑side JavaScript. For an SSG,
pulling in any unnecessary client JavaScript by default is unwise.&lt;/p&gt;
&lt;p&gt;So I chose a lightweight solution: JSX powered by &lt;strong&gt;Hono/JSX&lt;/strong&gt; for templates. It
lets us write JSX, render it on the server, and output an HTML string while
keeping type safety. For pure static pages the resulting HTML files contain no
framework‑related client code.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-tsx notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;HtmlArticle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; html &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; __html&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; html &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Hono&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HtmlArticle&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;markdownRenderer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdown&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&quot;step-3--output-as-website-files&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#step-3--output-as-website-files&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Step 3 – Output as website files&lt;/h3&gt;
&lt;p&gt;After defining content and templates we need to define the site structure (aka
routes). Since we’re using &lt;strong&gt;Hono/JSX&lt;/strong&gt;, we can declare routes with an HTTP
framework—Hono fits the bill. With Hono we can also spin up a dev server easily.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Later we could also use this approach for Server‑Side Rendering (SSR), but for
now we focus on SSG.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hono provides a &lt;code&gt;toSSG&lt;/code&gt; method that pre‑renders the Hono app’s routes into
static files at build time, allowing us to output static files directly from
Hono.&lt;/p&gt;
&lt;h2 id=&quot;styling-the-blog&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#styling-the-blog&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Styling the Blog&lt;/h2&gt;
&lt;p&gt;Applying CSS in JSX has always been tricky—CSS Modules, until atomic solutions
like &lt;strong&gt;TailwindCSS&lt;/strong&gt; arrived.&lt;/p&gt;
&lt;p&gt;We chose &lt;strong&gt;UnoCSS&lt;/strong&gt; as our CSS solution because it generates atomic CSS on
demand and offers a programmable generator API that can compile class names into
CSS code.&lt;/p&gt;
&lt;div class=&quot;not-prose highlight my-8 max-w-full overflow-hidden rounded border border-border bg-muted text-foreground highlight-source-ts notranslate&quot;&gt;&lt;pre class=&quot;not-prose m-0 max-w-full overflow-x-auto bg-muted p-5 font-mono text-sm text-foreground leading-6&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; createGenerator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; presetWind4 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;unocss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; uno &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createGenerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  presets&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;presetWind4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compileAtomicCss&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; css &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; uno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; css&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the default blog style I went with a classic Macintosh look. We’re planning
to use Figma and create a corresponding UI component library to deliver a
retro‑styled blog theme.&lt;/p&gt;
&lt;h2 id=&quot;future-plans&quot;&gt;&lt;a class=&quot;anchor&quot; aria-hidden=&quot;true&quot; tabindex=&quot;-1&quot; href=&quot;#future-plans&quot;&gt;&lt;svg class=&quot;octicon octicon-link&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Future Plans&lt;/h2&gt;
&lt;p&gt;You may notice that this blog uses &lt;strong&gt;no client‑side JavaScript&lt;/strong&gt;, which is
exactly what I wanted. A static blog doesn’t need any client JavaScript.&lt;/p&gt;
&lt;p&gt;The source code is available on GitHub.&lt;/p&gt;
&lt;p&gt;Eventually we hope to turn this project into a real framework and release our
own UI component library, along with features such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plugin‑based RSS and Sitemap generation&lt;/li&gt;
&lt;li&gt;Search and comment systems based on HTML Form&lt;/li&gt;
&lt;li&gt;An online compiler&lt;/li&gt;
&lt;li&gt;Possible full‑stack SSR support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because we don’t rely on &lt;strong&gt;Vite&lt;/strong&gt; and we avoid dependencies that only work in a
&lt;strong&gt;Node.js&lt;/strong&gt; environment, this framework could even run directly in the browser
in the future.&lt;/p&gt;
</description></item></channel></rss>