<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rick Cogley</title>
    <description>The latest articles on DEV Community by Rick Cogley (@rickcogley).</description>
    <link>https://dev.to/rickcogley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F738061%2Fff39689f-603b-4fcd-a3b1-c68ca7609b3f.jpeg</url>
      <title>DEV Community: Rick Cogley</title>
      <link>https://dev.to/rickcogley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rickcogley"/>
    <language>en</language>
    <item>
      <title>Engineering Backpressure: Keeping AI-Generated Code Honest Across 10 SvelteKit Repos</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:28:24 +0000</pubDate>
      <link>https://dev.to/rickcogley/engineering-backpressure-keeping-ai-generated-code-honest-across-10-sveltekit-repos-3foi</link>
      <guid>https://dev.to/rickcogley/engineering-backpressure-keeping-ai-generated-code-honest-across-10-sveltekit-repos-3foi</guid>
      <description>&lt;p&gt;I manage about ten SvelteKit repositories deployed on Cloudflare Workers, and leveraged Anthropic's Claude Code to do it. Generally speaking, AI coding assistance can be fast and capable, especially if you already know how to code, but precisely because they are so fast, they can be — if you're not careful — consistently wrong in ways that are hard to spot.&lt;/p&gt;

&lt;p&gt;Not wrong as in "the code doesn't work." Wrong as in: it uses &lt;code&gt;.parse()&lt;/code&gt; instead of &lt;code&gt;.safeParse()&lt;/code&gt;, it interpolates variables into D1 SQL strings instead of using &lt;code&gt;.bind()&lt;/code&gt;, it fires off database mutations without checking the result, it nests four levels of async logic inside a load function that should have been split into helpers. The code works. It passes TypeScript.&lt;/p&gt;

&lt;p&gt;The problem is that if you add guidance to your &lt;code&gt;CLAUDE.md&lt;/code&gt; file (or other coding agents' guide files) such as "always use &lt;code&gt;safeParse()&lt;/code&gt;" and "never interpolate SQL", those are just suggestions, not constraints. The AI reads them, and might follow them, but also it might not. There is no compiler error when it forgets. The instruction is non-deterministic, so the compliance is non-deterministic too.&lt;/p&gt;

&lt;p&gt;This post is about how I made compliance deterministic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Principle
&lt;/h2&gt;

&lt;p&gt;In hydraulics, 'backpressure' is resistance applied to a flow to regulate it. In software, the equivalent is making bad patterns structurally impossible rather than merely discouraged.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; instruction is a sign on the pipe that says "please don't overflow." A lint rule is a pressure valve that rejects the bad flow automatically. The sign might be ignored; the valve cannot.&lt;/p&gt;

&lt;p&gt;The goal: migrate every "always" and "never" statement out of documentation into something mechanical — a type, a lint rule, a test, or a structural check. What remains in &lt;code&gt;CLAUDE.md&lt;/code&gt; should be context and intent — the &lt;em&gt;why&lt;/em&gt;, not the &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Linting Passes
&lt;/h2&gt;

&lt;p&gt;Each linting tool has a different strength.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;oxlint&lt;/strong&gt; (~50ms) — Rust-based, checks ~200 universal JS/TS rules. Fast correctness pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ESLint + Svelte plugin&lt;/strong&gt; — Understands &lt;code&gt;.svelte&lt;/code&gt; files as a whole. Hosts custom backpressure rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-raw-html&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;{@html expr}&lt;/code&gt; without sanitization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-binding-leak&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Returning &lt;code&gt;platform.env.*&lt;/code&gt; from load functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-schema-parse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.parse()&lt;/code&gt; instead of &lt;code&gt;.safeParse()&lt;/code&gt; on Zod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-silent-catch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Empty catch blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These encode exactly the "always" and "never" statements that used to live only in prose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ast-grep&lt;/strong&gt; — The newest and most interesting. Uses &lt;a href="https://tree-sitter.github.io/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt; to match code by &lt;em&gt;structure&lt;/em&gt;. Rules are declarative YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n-plus-one-query-map&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TypeScript&lt;/span&gt;
&lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;
&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
  &lt;span class="s"&gt;Potential N+1 query: database call inside .map().&lt;/span&gt;
  &lt;span class="s"&gt;Use db.batch() or WHERE IN instead.&lt;/span&gt;
&lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$ARR.map($$$ARGS)&lt;/span&gt;
  &lt;span class="na"&gt;has&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$DB.prepare($$$SQL)&lt;/span&gt;
    &lt;span class="na"&gt;stopBy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches something neither oxlint nor ESLint can express: a database query nested inside an array iteration. In a SvelteKit load function hitting Cloudflare D1, this is a performance disaster. The AI generates this regularly because it looks correct and works fine on small datasets.&lt;/p&gt;

&lt;p&gt;Full ast-grep rule set:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql-injection-d1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Template literals in &lt;code&gt;db.prepare()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;n-plus-one-query-*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DB calls inside &lt;code&gt;.map()&lt;/code&gt;, &lt;code&gt;.forEach()&lt;/code&gt;, &lt;code&gt;for...of&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unbounded-query-all&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.all()&lt;/code&gt; without LIMIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unchecked-db-run&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fire-and-forget &lt;code&gt;.run()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;empty-catch-block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Silent error swallowing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each corresponds to a mistake I found in AI-generated code that passed both oxlint and ESLint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Upstream
&lt;/h2&gt;

&lt;p&gt;Mechanical enforcement handles known patterns. But Svelte and Cloudflare ship constantly — SvelteKit has had 50+ releases since 5.0. The AI doesn't know about features released last week.&lt;/p&gt;

&lt;p&gt;I built a "what's new" audit: a shell script fetches my &lt;a href="https://svelte.cogley.jp/feeds/patterns.json" rel="noopener noreferrer"&gt;SvelteKit patterns feed&lt;/a&gt; (a JSON Feed 1.1 endpoint with grep-friendly search signatures) and the Cloudflare changelog RSS. It filters CF entries to only the products each repo uses (by reading &lt;code&gt;wrangler.jsonc&lt;/code&gt; bindings), then searches source code for legacy patterns.&lt;/p&gt;

&lt;p&gt;Output: "repo x has 3 files still using &lt;code&gt;writable()&lt;/code&gt; stores — &lt;code&gt;$state&lt;/code&gt; class replacement available since Svelte 5.29."&lt;/p&gt;

&lt;p&gt;When the audit discovers features not in the feed, it flags those as gaps. The feed stays current because the audit tells us when it's behind. The audit runs in about 10 seconds across all ten repos — it's a shell script, not an AI call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Centralize, Then Distribute
&lt;/h2&gt;

&lt;p&gt;All rules, configs, and workflows live in one &lt;code&gt;.github&lt;/code&gt; repo. A sync script distributes to ten consumer repos. One &lt;code&gt;sync-all.sh&lt;/code&gt; updates everything. No drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Catches
&lt;/h2&gt;

&lt;p&gt;Since deploying, the ast-grep rules alone have flagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Template literal SQL injection in load functions the AI generated when asked to "add a search filter"&lt;/li&gt;
&lt;li&gt;N+1 patterns where the AI helpfully awaited each item individually&lt;/li&gt;
&lt;li&gt;Unbounded &lt;code&gt;.all()&lt;/code&gt; queries that worked in development (5 rows) and would have timed out in production (50,000 rows)&lt;/li&gt;
&lt;li&gt;Empty catch blocks where D1 errors vanished silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None produced TypeScript errors. None were caught by oxlint or ESLint. All would have shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit your &lt;code&gt;CLAUDE.md&lt;/code&gt; or other guidance files for "always" and "never" statements.&lt;/strong&gt; Each is a candidate for a lint rule or type constraint. Do that, then remove the prose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Linters have different strengths.&lt;/strong&gt; oxlint (fast, shallow) + ESLint (framework-aware) + ast-grep (structural) at 50ms + 2s + 200ms is still faster than one human review.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track upstream releases programmatically.&lt;/strong&gt; A structured feed with search signatures turns "what's new?" into a deterministic scan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centralize, then distribute.&lt;/strong&gt; Ten repos with synced configs means zero drift.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AI writes code faster than you can review it. The answer isn't slower AI — it's making the codebase reject bad patterns automatically, immediately, and without judgment.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The systems described here are specific to SvelteKit on Cloudflare Workers, but the principle — mechanical enforcement over written instructions — applies to any AI-assisted codebase. My svelte &lt;a href="https://svelte.cogley.jp/feeds/patterns.json" rel="noopener noreferrer"&gt;patterns feed&lt;/a&gt; is public if you want to point your own tooling at it.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/engineering-backpressure-ai-code-quality" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sveltekit</category>
      <category>ai</category>
      <category>typescript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Learn the Hard Way First: Why New Developers Should Build Skills Before Leaning on AI</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sat, 28 Mar 2026 01:59:54 +0000</pubDate>
      <link>https://dev.to/rickcogley/learn-the-hard-way-first-why-new-developers-should-build-skills-before-leaning-on-ai-ibk</link>
      <guid>https://dev.to/rickcogley/learn-the-hard-way-first-why-new-developers-should-build-skills-before-leaning-on-ai-ibk</guid>
      <description>&lt;p&gt;Welcome to your first job, freshers. You've probably already used ChatGPT or Copilot to get through assignments. I'm not here to tell you to avoid AI forever. But &lt;strong&gt;if you skip the struggle now, you'll pay for it for years&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garbage In, Garbage Out
&lt;/h2&gt;

&lt;p&gt;GIGO applies perfectly to AI-assisted development. The quality of your prompts depends entirely on what you already know. If you've never written a SQL query by hand, you won't notice when the generated one has a subtle N+1 problem. If you've never debugged a race condition, you won't even know to ask about concurrency.&lt;/p&gt;

&lt;p&gt;The AI doesn't know what you don't know. It will confidently hand you something that &lt;em&gt;looks&lt;/em&gt; right. Your job is to know enough to catch when it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "It Works" Trap
&lt;/h2&gt;

&lt;p&gt;New devs fall into a pattern: prompt → paste → it runs → ship. But "it works" is the lowest bar. Code that works can still be unmaintainable, insecure, brittle, or wildly inefficient.&lt;/p&gt;

&lt;p&gt;As &lt;a href="https://en.wikipedia.org/wiki/Edsger_W._Dijkstra" rel="noopener noreferrer"&gt;Edsger Dijkstra&lt;/a&gt; put it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;History is full of "working" code that caused disasters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ariane 5 (1996)&lt;/strong&gt; — Code reused from Ariane 4 had a 64-bit to 16-bit cast that overflowed on the new rocket's trajectory. Both redundant nav units crashed (identical code). The rocket exploded 37 seconds after launch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Knight Capital (2012)&lt;/strong&gt; — A deploy missed 1 of 8 servers. Dead code from a deprecated feature got accidentally reactivated. $440 million lost in 45 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Therac-25 (1985–87)&lt;/strong&gt; — A race condition delivered 100x radiation doses, killing three patients. Previous models had the same bug, but hardware interlocks caught it. When the hardware safety was removed and software was trusted alone, people died.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the production incident hits at 2 AM, "the AI wrote it" isn't a debugging strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "The Hard Way" Means
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Write it yourself first, then compare.&lt;/strong&gt; In his 2016 TED talk, &lt;a href="https://en.wikipedia.org/wiki/Linus_Torvalds" rel="noopener noreferrer"&gt;Linus Torvalds&lt;/a&gt; showed two C implementations for linked list removal — both produce identical output, but the elegant one eliminates a special case entirely. He called this "good taste":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Sometimes you can see a problem in a different way and rewrite it so that a special case goes away and becomes the normal case. And that's good code."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You only develop that taste by writing the ugly version first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug without AI.&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/Brian_Kernighan" rel="noopener noreferrer"&gt;Brian Kernighan&lt;/a&gt; wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If AI writes at the limit of your understanding, you've guaranteed you can't debug it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understand the fundamentals.&lt;/strong&gt; Data structures, HTTP, how databases work. These aren't academic exercises — they're the vocabulary for asking good questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read other people's code.&lt;/strong&gt; Pattern recognition compounds over your entire career. AI can't shortcut this for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn to test properly.&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/Kent_Beck" rel="noopener noreferrer"&gt;Kent Beck&lt;/a&gt;'s progression:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Make it work, make it right, make it fast."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Make it work" is step one of three — not the finish line.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Ramp
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First 3 months&lt;/strong&gt;: Minimize AI coding assistance. Write and debug by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Months 4–6&lt;/strong&gt;: Use AI for &lt;em&gt;exploration&lt;/em&gt; — explaining concepts, showing tradeoffs. Teacher, not ghostwriter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Months 6–12&lt;/strong&gt;: Use AI for &lt;em&gt;acceleration&lt;/em&gt; — boilerplate, test suggestions, design rubber-ducking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always&lt;/strong&gt;: If AI generates code you don't understand, stop. Don't use it until you do.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Multiplier Effect
&lt;/h2&gt;

&lt;p&gt;AI multiplies whatever you give it. Deep understanding becomes extraordinary productivity. Bad assumptions become extraordinary damage. Knight Capital didn't lose $440 million from one mistake — they lost it because a chain of "it works" assumptions got multiplied faster than any human could intervene.&lt;/p&gt;

&lt;p&gt;Build the foundation first. Learn the hard way. It's the fastest path to becoming the kind of developer who can actually wield AI effectively — rather than the kind who feeds it garbage and ships the output.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full version of this article — with additional disaster case studies, quotes from Dijkstra, Hoare, Torvalds, Kernighan, and Fowler, and a section on Japan's April new-graduate season — is on &lt;a href="https://cogley.jp/articles/code-the-hard-way-before-using-ai" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Audit Your SvelteKit Codebase with a JSON Feed of 34 Svelte 5 Patterns</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Thu, 26 Mar 2026 21:09:50 +0000</pubDate>
      <link>https://dev.to/rickcogley/audit-your-sveltekit-codebase-with-a-json-feed-of-34-svelte-5-patterns-4l6e</link>
      <guid>https://dev.to/rickcogley/audit-your-sveltekit-codebase-with-a-json-feed-of-34-svelte-5-patterns-4l6e</guid>
      <description>&lt;p&gt;My &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;Migrate to Svelte 5&lt;/a&gt; site started as a side-by-side reference for developers wanting to convert to Svelte 5, from React, Vue, or Angular. It maps concepts across frameworks: "your &lt;code&gt;useState&lt;/code&gt; is Svelte's &lt;code&gt;$state&lt;/code&gt;," "your &lt;code&gt;useEffect&lt;/code&gt; is &lt;code&gt;$effect&lt;/code&gt;," and so on — about 300 entries covering syntax, architecture, and ecosystem. &lt;/p&gt;

&lt;p&gt;That's useful, for a human reading the site. But I kept running into a different scenario: working in a SvelteKit codebase with Claude Code and wanting to ask "go check what's new in Svelte and see what applies here." Because that kind of quick synthesis is what AI Agents are good at. The data was all on the site, but there was no machine-friendly way to query it for actionable patterns.&lt;/p&gt;

&lt;p&gt;So I added two things to the site: a &lt;strong&gt;structured patterns feed&lt;/strong&gt; and a &lt;strong&gt;browsable patterns page&lt;/strong&gt;. This post covers the thinking and the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Static Migration Guides
&lt;/h2&gt;

&lt;p&gt;A migration guide answers "how do I do X in Svelte?" That's a pull interaction — the developer has a question and looks up the answer. Useful, but limited to that style of questioning.&lt;/p&gt;

&lt;p&gt;What I wanted was a push interaction: "here are the 34 things Svelte shipped since 5.0 that you should know about, with exact code patterns to search for in your repo." An AI coding assistant could fetch this, grep the codebase, and say "you're still using &lt;code&gt;writable()&lt;/code&gt; stores in 12 files — here's the &lt;code&gt;$state&lt;/code&gt; class replacement." A bit of a pain for a human dev, but short work for an AI.&lt;/p&gt;

&lt;p&gt;The key insight: the migration guide already had the "from" and "to" for each pattern. It just needed to be structured as queryable data instead of prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a JSON Feed, Not a Custom Format
&lt;/h2&gt;

&lt;p&gt;My first attempt was a &lt;code&gt;/llms-whatsnew.txt&lt;/code&gt; endpoint with bespoke SEARCH FOR / REPLACE WITH sections. It worked, but it was a made-up format pretending to be part of a standard. The &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt convention&lt;/a&gt; defines only two files — &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;. Adding &lt;code&gt;llms-whatsnew.txt&lt;/code&gt; was inventing a spec, and agents would not be able to figure that out unless you specify each time.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.jsonfeed.org/version/1.1/" rel="noopener noreferrer"&gt;JSON Feed 1.1&lt;/a&gt; solved this properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's a real spec with existing tooling (feed readers, aggregators, automation)&lt;/li&gt;
&lt;li&gt;Extension fields (underscore-prefixed) carry custom data without breaking compatibility&lt;/li&gt;
&lt;li&gt;Any tool that understands JSON Feed can consume it; the &lt;code&gt;_svelte_pattern&lt;/code&gt; extensions are bonus data for tools that know about them&lt;/li&gt;
&lt;li&gt;One endpoint serves feed readers, AI agents, and automation equally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision came from asking a simple question: if someone discovers this endpoint without documentation, can they use it? With JSON Feed, yes — any feed reader picks it up. With a custom &lt;code&gt;.txt&lt;/code&gt; format, no, not really.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Patterns Data Structure
&lt;/h2&gt;

&lt;p&gt;Each pattern in &lt;code&gt;patterns.ts&lt;/code&gt; represents one actionable Svelte feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SveltePattern&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title_ja&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "svelte@5.29.0" or "sveltekit@2.55.0"&lt;/span&gt;
  &lt;span class="nl"&gt;date_added&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;architecture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecosystem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tooling&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;frameworks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[];&lt;/span&gt;
  &lt;span class="nl"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;summary_ja&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;search_signatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// grep-friendly patterns&lt;/span&gt;
  &lt;span class="nl"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// modern Svelte 5 code&lt;/span&gt;
  &lt;span class="nl"&gt;release_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// GitHub release page&lt;/span&gt;
  &lt;span class="nl"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;search_signatures&lt;/code&gt; field is what makes this useful for automation. Each entry is a string you can literally &lt;code&gt;grep&lt;/code&gt; for. An AI assistant reads these, runs them against your codebase, and finds where old patterns still live.&lt;/p&gt;

&lt;p&gt;Here's what an attachments pattern looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;attachments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Attachments Replace use: Actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte@5.29.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;search_signatures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use:action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use:tooltip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use:clickOutside&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from 'svelte/action'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`{@attach (element) =&amp;gt; {
  element.focus();
  return () =&amp;gt; { /* cleanup */ };
}}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;release_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/sveltejs/svelte/releases/tag/svelte%405.29.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Attachments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://svelte.dev/docs/svelte/@attach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;release_url&lt;/code&gt; links directly to the GitHub release page for the version that introduced this feature. Every pattern is traceable, then, to its exact release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Feeds, Two Audiences
&lt;/h2&gt;

&lt;p&gt;I ended up with two separate JSON Feed endpoints serving different purposes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/feeds/changelog.json&lt;/code&gt;&lt;/strong&gt; — what changed on the site itself, e.g. new mappings added, content updates, bug fixes. For subscribers who want to know when the reference itself is updated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/feeds/patterns.json&lt;/code&gt;&lt;/strong&gt; — the actionable patterns feed. Each item is a Svelte feature with search signatures, replacement code, and a link to the GitHub release. Supports filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/feeds/patterns.json?since=5.54          # Svelte 5.54+ patterns only
/feeds/patterns.json?category=syntax     # Just syntax patterns
/feeds/patterns.json?framework=react     # Patterns relevant to React migrants
/feeds/patterns.json?lang=ja             # Japanese summaries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separation here is important, because the audiences are different. Someone subscribing to the changelog wants to know "did this reference get updated?" Someone (or something) querying the patterns feed wants to know "what Svelte features should I adopt?"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Feed Extension
&lt;/h3&gt;

&lt;p&gt;JSON Feed 1.1 supports extension fields prefixed with an underscore. Standard feed readers ignore them; tools that understand the schema use them. The &lt;code&gt;_svelte_pattern&lt;/code&gt; extension carries the structured data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://jsonfeed.org/version/1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Svelte 5 Migration Patterns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_svelte_patterns_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"filtered_patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"usage_hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Each item._svelte_pattern.search_signatures contains grep-friendly strings..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://svelte.cogley.jp/patterns/attachments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attachments Replace use: Actions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date_published"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-08T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"syntax"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_svelte_pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attachments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"since"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"svelte@5.29.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"syntax"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"frameworks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"search_signatures"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"use:action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"use:tooltip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"use:clickOutside"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"replacement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{@attach (element) =&amp;gt; { ... }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"use: still works but is legacy. Attachments work on components too."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"release_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/sveltejs/svelte/releases/tag/svelte%405.29.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Attachments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://svelte.dev/docs/svelte/@attach"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_svelte_patterns_meta&lt;/code&gt; at the feed level includes a &lt;code&gt;usage_hint&lt;/code&gt; that tells any AI agent reading the feed exactly how to use the search signatures. Self-documenting data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Patterns Page
&lt;/h2&gt;

&lt;p&gt;The feed is machine-readable. For humans, there's &lt;a href="https://svelte.cogley.jp/patterns" rel="noopener noreferrer"&gt;&lt;code&gt;/patterns&lt;/code&gt;&lt;/a&gt; — a filterable browser with dropdowns for version, category, and framework, plus free text search. Each card shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version badge and category/framework tags&lt;/li&gt;
&lt;li&gt;A bilingual summary&lt;/li&gt;
&lt;li&gt;Search signatures as copyable code badges&lt;/li&gt;
&lt;li&gt;The replacement code in a syntax-highlighted block&lt;/li&gt;
&lt;li&gt;Links to the GitHub release and Svelte documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It uses the same &lt;code&gt;patterns.ts&lt;/code&gt; data file that the feed endpoint reads, so there's a single source of truth. The page is SSR'd on Cloudflare Workers — no client-side fetch needed.&lt;/p&gt;

&lt;p&gt;The filters are reactive using Svelte 5's &lt;code&gt;$derived.by()&lt;/code&gt;. Changing any dropdown or typing in the search box immediately narrows the visible patterns. The text search matches across titles, summaries, notes, code, and search signatures in both languages.&lt;/p&gt;

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

&lt;p&gt;At the time of this post, there are 34 patterns spanning Svelte 5.0 through 5.55 and SvelteKit 2.10 through 2.55:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core runes (5.0):&lt;/strong&gt; &lt;code&gt;$state&lt;/code&gt;, &lt;code&gt;$derived&lt;/code&gt;, &lt;code&gt;$effect&lt;/code&gt;, &lt;code&gt;$props&lt;/code&gt;/&lt;code&gt;$bindable&lt;/code&gt;, snippets, &lt;code&gt;onclick&lt;/code&gt; handlers, &lt;code&gt;.svelte.ts&lt;/code&gt; modules, &lt;code&gt;$inspect&lt;/code&gt;, &lt;code&gt;$state.raw&lt;/code&gt;/&lt;code&gt;.snapshot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental releases:&lt;/strong&gt; error boundaries (5.3), &lt;code&gt;{#each}&lt;/code&gt; without &lt;code&gt;as&lt;/code&gt; (5.4), exportable snippets (5.5), &lt;code&gt;MediaQuery&lt;/code&gt; class (5.7), Spring/Tween classes (5.8), reactive window values (5.11), ClassValue + object/array class syntax (5.16), &lt;code&gt;$props.id()&lt;/code&gt; (5.20), writable &lt;code&gt;$derived&lt;/code&gt; (5.25), attachments (5.29), snippet generics (5.30), &lt;code&gt;fromAction()&lt;/code&gt; (5.32), &lt;code&gt;getAbortSignal()&lt;/code&gt; (5.35), async components (5.36), &lt;code&gt;createContext&lt;/code&gt; (5.50), TrustedHTML (5.52), parametric compiler options (5.54), motion type exports (5.55)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SvelteKit:&lt;/strong&gt; &lt;code&gt;init&lt;/code&gt; hook (2.10), &lt;code&gt;transport&lt;/code&gt; hook (2.11), &lt;code&gt;$app/state&lt;/code&gt; (2.12), async reroute (2.18), &lt;code&gt;getRequestEvent()&lt;/code&gt; (2.20), remote functions (2.27), SSR error boundaries (2.54), type-narrowed params (2.55)&lt;/p&gt;

&lt;p&gt;Each links to its GitHub release page, so you can trace the exact changelog entry, if and as needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using It with an AI Assistant
&lt;/h2&gt;

&lt;p&gt;The practical use case: you're in a SvelteKit codebase and want to modernize. Tell your AI assistant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fetch &lt;a href="https://svelte.cogley.jp/feeds/patterns.json" rel="noopener noreferrer"&gt;https://svelte.cogley.jp/feeds/patterns.json&lt;/a&gt; and check what patterns from this feed apply to this repo."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The assistant fetches the feed, reads the &lt;code&gt;search_signatures&lt;/code&gt; from each pattern, greps the codebase, and reports which old patterns it found with the modern replacements. No manual searching through docs.&lt;/p&gt;

&lt;p&gt;For a more targeted audit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fetch &lt;a href="https://svelte.cogley.jp/feeds/patterns.json?since=5.29" rel="noopener noreferrer"&gt;https://svelte.cogley.jp/feeds/patterns.json?since=5.29&lt;/a&gt; and see if we're using any pre-5.29 patterns that have better alternatives now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also filter by what you're migrating from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fetch &lt;a href="https://svelte.cogley.jp/feeds/patterns.json?framework=react" rel="noopener noreferrer"&gt;https://svelte.cogley.jp/feeds/patterns.json?framework=react&lt;/a&gt; and show me the patterns relevant to this React-to-Svelte migration."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The feed's &lt;code&gt;_svelte_patterns_meta.usage_hint&lt;/code&gt; field tells the assistant exactly how to interpret the data, so it works even without prior knowledge of the feed's structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Results
&lt;/h2&gt;

&lt;p&gt;I pointed Claude Code at the patterns feed across several production repositories and asked it to audit each one. Here's what happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixes Applied Immediately
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pub-cogley&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;onMount&lt;/code&gt;/&lt;code&gt;onDestroy&lt;/code&gt; lifecycle, manual &lt;code&gt;AbortController&lt;/code&gt;, duplicated utils&lt;/td&gt;
&lt;td&gt;Converted 19 lifecycle calls to &lt;code&gt;$effect&lt;/code&gt; across 4 apps, extracted shared utils, added cookie &lt;code&gt;secure&lt;/code&gt; flag&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pulse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom &lt;code&gt;window.dispatchEvent('org-switched')&lt;/code&gt; pattern&lt;/td&gt;
&lt;td&gt;Replaced with &lt;code&gt;$state&lt;/code&gt;-based reactive signal in &lt;code&gt;.svelte.ts&lt;/code&gt; module&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;codex&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$app/stores&lt;/code&gt; imports (&lt;code&gt;$page&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Converted to &lt;code&gt;$app/state&lt;/code&gt; with &lt;code&gt;$derived()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;periodic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$app/stores&lt;/code&gt; + &lt;code&gt;writable()&lt;/code&gt; language store&lt;/td&gt;
&lt;td&gt;Converted to &lt;code&gt;$app/state&lt;/code&gt; (9 files) + rewrote language store as &lt;code&gt;.svelte.ts&lt;/code&gt; with &lt;code&gt;$state&lt;/code&gt; rune (60 consumers)&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;courier&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$app/stores&lt;/code&gt; + &lt;code&gt;writable()&lt;/code&gt; language store + unvalidated route params&lt;/td&gt;
&lt;td&gt;Converted to &lt;code&gt;$app/state&lt;/code&gt; (3 files), rewrote language store (9 consumers), added param matchers for route type safety&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's 118 files modernized across 5 SvelteKit codebases, but I also applied it to a non-svelte project, and a greenfield project that used the feed as a day-one adoption checklist.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Was Already Modern
&lt;/h3&gt;

&lt;p&gt;Every repo was already using core Svelte 5 patterns: &lt;code&gt;$state&lt;/code&gt;, &lt;code&gt;$derived&lt;/code&gt;, &lt;code&gt;$effect&lt;/code&gt;, &lt;code&gt;$props&lt;/code&gt;, snippets, &lt;code&gt;onclick&lt;/code&gt; handlers, &lt;code&gt;use:enhance&lt;/code&gt;. The feed correctly identified these as "no action needed" and focused on the gaps — mostly &lt;code&gt;$app/stores&lt;/code&gt; → &lt;code&gt;$app/state&lt;/code&gt; conversions and lingering &lt;code&gt;writable()&lt;/code&gt; stores.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Greenfield Case: Mame
&lt;/h3&gt;

&lt;p&gt;One repo — mame, an internal learning platform started the same day — used the feed &lt;em&gt;proactively&lt;/em&gt; rather than retroactively. Instead of finding legacy code to fix, it used the patterns feed as a kind of checklist for what to adopt from the start. The result: &lt;code&gt;createContext&amp;lt;T&amp;gt;()&lt;/code&gt; for type-safe context (eliminated prop drilling across 18 files), &lt;code&gt;$props.id()&lt;/code&gt; for SSR-safe form IDs, &lt;code&gt;MediaQuery&lt;/code&gt; class for reactive breakpoints, and &lt;code&gt;svelte/reactivity/window&lt;/code&gt; for reactive viewport values — all patterns that the existing repos had flagged as "future opportunities" but hadn't adopted yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Nexus Case
&lt;/h3&gt;

&lt;p&gt;One repo — nexus — is a pure Hono API worker with zero &lt;code&gt;.svelte&lt;/code&gt; files. The feed audit correctly reported "N/A" for all Svelte patterns. It did find deprecated shared docs containing Svelte 4 examples that had been migrated to another repo, which were cleaned up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Opportunities
&lt;/h3&gt;

&lt;p&gt;Every repo got a tailored list of patterns that are available but not yet adopted — type-narrowed route params (SvelteKit 2.55), SSR error boundaries (2.54), &lt;code&gt;createContext&amp;lt;T&amp;gt;()&lt;/code&gt; (5.50), &lt;code&gt;getAbortSignal()&lt;/code&gt; (5.35). Not urgent, but now documented as backlog items. The "Not Applicable" column was equally useful — the feed correctly identified cases where patterns don't apply, like &lt;code&gt;getAbortSignal()&lt;/code&gt; in repos where fetches are user-triggered button clicks rather than &lt;code&gt;$effect&lt;/code&gt; blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Feed Endpoints
&lt;/h3&gt;

&lt;p&gt;The feed endpoints are SvelteKit server routes returning &lt;code&gt;json()&lt;/code&gt; responses with CORS headers and 1-hour cache. Both are at &lt;code&gt;/feeds/&lt;/code&gt; — a dedicated namespace that won't conflict with page routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Version Filtering
&lt;/h3&gt;

&lt;p&gt;The patterns feed supports version filtering via the &lt;code&gt;since&lt;/code&gt; query parameter. The implementation uses semver comparison on the &lt;code&gt;since&lt;/code&gt; field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;?since=5.54&lt;/code&gt; matches all &lt;code&gt;svelte@5.54.0+&lt;/code&gt; patterns but not &lt;code&gt;sveltekit@&lt;/code&gt; patterns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?since=kit@2.55&lt;/code&gt; matches all &lt;code&gt;sveltekit@2.55.0+&lt;/code&gt; patterns but not &lt;code&gt;svelte@&lt;/code&gt; patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This lets you ask "what's new since version X?" for either package independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feed Autodiscovery
&lt;/h3&gt;

&lt;p&gt;Feed autodiscovery is handled by &lt;code&gt;&amp;lt;link rel="alternate" type="application/feed+json"&amp;gt;&lt;/code&gt; tags in the SvelteKit layout. Any feed reader that visits any page on the site will discover both feeds automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bilingual Support
&lt;/h3&gt;

&lt;p&gt;Both feeds support &lt;code&gt;?lang=ja&lt;/code&gt; to return Japanese titles, summaries, and notes where available. The patterns data is bilingual at the source level — every pattern has both &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;title_ja&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt; and &lt;code&gt;summary_ja&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layout and Navigation
&lt;/h3&gt;

&lt;p&gt;The patterns page sits alongside the existing home (migration mappings), about, and changelog pages. Feed links appear in the global footer on every page, and the changelog page has a JSON Feed badge in its header. The layout includes theme and language toggles in the nav bar, accessible from every page.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Fits Together
&lt;/h2&gt;

&lt;p&gt;The migration guide now has three machine-readable interfaces, each for a different use case:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Audience&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/llms-full.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LLMs reading the whole site&lt;/td&gt;
&lt;td&gt;"Tell me about Svelte 5 migration"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/feeds/patterns.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AI coding assistants&lt;/td&gt;
&lt;td&gt;"Audit this codebase for modernization"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebMCP tools&lt;/td&gt;
&lt;td&gt;Browser-based AI agents&lt;/td&gt;
&lt;td&gt;"What's the Svelte equivalent of useEffect?"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The patterns feed fills the gap between "read everything" (llms-full.txt) and "answer one question" (WebMCP). It's the "here's what's actionable" layer — structured enough for automation, browsable enough for humans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;h2&gt;
  
  
  This builds on the &lt;a href="https://cogley.jp/articles/migrate-to-svelte-5-interactive-reference" rel="noopener noreferrer"&gt;original migration guide article&lt;/a&gt; and the &lt;a href="https://cogley.jp/articles/enabling-webmcp-tools-sveltekit-migration-reference" rel="noopener noreferrer"&gt;WebMCP tools implementation&lt;/a&gt;. The patterns feed complements WebMCP — WebMCP is for browser-based AI agents interacting with the page, while the JSON Feed is for CLI tools and coding assistants that can fetch URLs directly.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/svelte-patterns-feed-ai-codebase-audits" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>svelte</category>
    </item>
    <item>
      <title>Cloudflare Dynamic Workers: Sandboxed Code Execution at the Edge</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Thu, 26 Mar 2026 04:30:42 +0000</pubDate>
      <link>https://dev.to/rickcogley/cloudflare-dynamic-workers-sandboxed-code-execution-at-the-edge-3ekn</link>
      <guid>https://dev.to/rickcogley/cloudflare-dynamic-workers-sandboxed-code-execution-at-the-edge-3ekn</guid>
      <description>&lt;p&gt;I needed to run user-defined JavaScript templates from a database — code that formats RSS feed items into social media posts. The templates could be anything: hand-written, AI-generated, pasted from a blog. Running arbitrary code strings inside my production Worker, with access to D1 databases and R2 buckets, wasn't an option. &lt;/p&gt;

&lt;p&gt;Cloudflare's &lt;a href="https://blog.cloudflare.com/dynamic-workers/" rel="noopener noreferrer"&gt;Dynamic Workers&lt;/a&gt;, released in March 2026, solved this. They let a parent Worker spawn new Workers at runtime from code strings, each in its own V8 isolate with explicitly controlled access. I wired them into my publishing stack in an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Before Dynamic Workers
&lt;/h2&gt;

&lt;p&gt;If you had a JavaScript function stored in a database and wanted to execute it inside a Cloudflare Worker, you had three options — all bad:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;new Function()&lt;/code&gt; / &lt;code&gt;eval()&lt;/code&gt;&lt;/strong&gt; — runs the code inside your Worker process, with full access to every binding, every secret, and the open internet. One malicious template and your D1 database, R2 buckets, and API keys are exposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String interpolation only&lt;/strong&gt; — treat templates as dumb mustache-style patterns (&lt;code&gt;{{title}} - {{link}}&lt;/code&gt;). Safe, but you can't express logic: no "skip items without titles," no conditional formatting, no length-based truncation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External execution service&lt;/strong&gt; — ship the code to a separate sandboxed API. Adds latency, another deployment to maintain, and a network hop that defeats the edge-computing point of Workers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dynamic Workers give you option 1's full JavaScript expressiveness with option 3's isolation, at option 2's simplicity. The code runs in a separate V8 isolate on the same machine, with zero access to anything you don't explicitly hand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How They Work
&lt;/h2&gt;

&lt;p&gt;The parent Worker creates a new Worker from a code string. Each spawned worker gets its own V8 sandbox, starts in milliseconds, and the parent controls exactly what it can touch.&lt;/p&gt;

&lt;p&gt;The API is minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LOADER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;compatibilityDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-21&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mainModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transform.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transform.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;codeString&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;globalOutbound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// block all network access&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntrypoint&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things matter here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;globalOutbound: null&lt;/code&gt;&lt;/strong&gt; — the spawned worker can't make HTTP requests. No phoning home, no data exfiltration, no SSRF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selective binding exposure&lt;/strong&gt; — the parent decides which bindings (D1, R2, KV) to pass. Pass nothing and the sandbox is a pure function: data in, data out.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I'm Using Them: Auto-Post RSS Feed Templates
&lt;/h2&gt;

&lt;p&gt;My publishing system (&lt;a href="https://cogley.jp" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;) is a monorepo with a Hono API on Cloudflare Workers, backed by D1, R2, KV, Vectorize, and several Workflows. It handles micro posts, long-form articles, feeds, and syndication to Bluesky, Nostr, and status.lol.&lt;/p&gt;

&lt;p&gt;I already had an RSS feed reader with a &lt;code&gt;feeds&lt;/code&gt; table that included unused &lt;code&gt;auto_post&lt;/code&gt; and &lt;code&gt;auto_post_template&lt;/code&gt; columns. The idea was always "when a feed gets a new item, create a post from it."&lt;/p&gt;

&lt;p&gt;Before Dynamic Workers, I would have had to hardcode the formatting logic for each feed directly in my Worker — a function like &lt;code&gt;formatEsoliaBlogPost()&lt;/code&gt; baked into the deployed code, redeployed every time I wanted to change the output format or add a new feed with different formatting. Now, I store a JavaScript function in a database column, and the cron executes it in a sandbox. Adding a new feed with completely different formatting is an API call, not a deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    A[Cron 15min] --&amp;gt; B[Fetch RSS]
    B --&amp;gt; C{New items?}
    C -- No --&amp;gt; Z[Done]
    C -- Yes --&amp;gt; D[Dynamic Worker
sandbox]
    D --&amp;gt; E{Valid output?}
    E -- null --&amp;gt; F[Mark skipped]
    E -- Yes --&amp;gt; G[Create post]
    G --&amp;gt; H[Generate OG image]
    H --&amp;gt; I[Syndicate to
Bluesky / Nostr / status.lol]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A cron runs every 15 minutes, refreshing subscribed RSS feeds&lt;/li&gt;
&lt;li&gt;When new items appear in a feed with &lt;code&gt;auto_post: true&lt;/code&gt;, each item goes to a Dynamic Worker&lt;/li&gt;
&lt;li&gt;The Dynamic Worker executes the template — a JavaScript function stored in D1&lt;/li&gt;
&lt;li&gt;The template returns structured data (content, stream, mood, visibility)&lt;/li&gt;
&lt;li&gt;The parent validates the output, creates a post, pre-generates an OG image, and triggers syndication to Bluesky/Nostr/status.lol&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Template
&lt;/h3&gt;

&lt;p&gt;Templates are arrow functions stored in the &lt;code&gt;auto_post_template&lt;/code&gt; column. This is the one I'm running for our company's bilingual tech blog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// skip items without titles&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;teaser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;147&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`**&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;**`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;teaser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;teaser&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ☕`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`\n\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`\n\n(Japanese version also available on the blog.)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tech&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;binoculars&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;syndicate_to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bluesky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nostr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;statuslog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function receives a plain data object — title, link, summary, content, author, and source feed metadata. It returns what the post should look like, or &lt;code&gt;null&lt;/code&gt; to skip the item.&lt;/p&gt;

&lt;p&gt;This is the kind of logic that string interpolation can't express. The template makes decisions: skip items without titles, truncate summaries longer than 150 characters at a word-friendly boundary, conditionally include the teaser line, choose a mood and syndication targets. With a mustache-style &lt;code&gt;{{title}} - {{link}}&lt;/code&gt; template, I'd need a separate config field for every one of those behaviors — and I'd still be hardcoding the logic in my Worker code. With Dynamic Workers, the template &lt;em&gt;is&lt;/em&gt; the logic, and changing it means updating one database row. No redeploy, no code change.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Security Model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TB
    subgraph Parent Worker
        A[Template code from D1] --&amp;gt; B[env.LOADER.load]
        F[Validate &amp;amp; sanitize output] --&amp;gt; G[Create post in D1]
    end
    subgraph Dynamic Worker sandbox
        C[Execute template function]
        D[No bindings
No network
No secrets]
    end
    B --&amp;gt; C
    C --&amp;gt; F
    style D fill:#fee,stroke:#c33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template code could be anything. The sandbox ensures it can't cause harm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No bindings&lt;/strong&gt;: zero Cloudflare bindings. No D1, no R2, no KV, no secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No network&lt;/strong&gt;: &lt;code&gt;globalOutbound: null&lt;/code&gt; blocks all outbound HTTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data in, data out&lt;/strong&gt;: the template receives a plain object and returns a plain object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output validation&lt;/strong&gt;: the parent validates the return value against a strict schema, sanitizes strings, strips control characters, and enforces allowed values for fields like &lt;code&gt;stream&lt;/code&gt; and &lt;code&gt;visibility&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the template throws or returns something invalid, the feed item is marked &lt;code&gt;skipped&lt;/code&gt; with a reason. The system moves on to the next item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;$0.002 per unique worker loaded per day (waived during beta), plus standard CPU time. For a cron that runs a handful of templates a few times a day, this rounds to zero against the AI inference costs I'm already paying for image analysis and content summarization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another Use Case: Live Code Migration
&lt;/h2&gt;

&lt;p&gt;I maintain &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;svelte.cogley.jp&lt;/a&gt;, an interactive reference for migrating from React, Vue, and Angular to Svelte 5. The site itself is static SSR — all mapping data baked into TypeScript files. But the people using it are actively converting code from one framework to another, and they'd benefit from a "paste your component, get a Svelte 5 version" feature.&lt;/p&gt;

&lt;p&gt;Dynamic Workers are how I'd build it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User pastes a React/Vue/Angular component&lt;/li&gt;
&lt;li&gt;The server sends it to Workers AI with a conversion prompt&lt;/li&gt;
&lt;li&gt;The AI-generated Svelte code runs in a Dynamic Worker sandbox to verify it parses — no phantom imports, no syntax errors&lt;/li&gt;
&lt;li&gt;The validated result goes back to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two layers of untrusted input — user-submitted code and AI-generated output — both handled by the same sandbox. &lt;code&gt;globalOutbound: null&lt;/code&gt; blocks the code from reaching the network, and the V8 isolate prevents it from touching anything else on the platform. This pattern applies anywhere you need a "paste your code, get a conversion" tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where They Don't Fit
&lt;/h2&gt;

&lt;p&gt;I evaluated Dynamic Workers across my entire stack. Three places where they're the wrong tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed application routes&lt;/strong&gt; — my Hono routes are stable, typed with Drizzle ORM, and benefit from static deployment. Making them dynamic would add indirection for no gain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable Workflows&lt;/strong&gt; — image processing, syndication delivery, and transcription use Cloudflare Workflows because they're multi-step and retriable. Dynamic Workers are ephemeral — wrong primitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Make everything dynamic"&lt;/strong&gt; — Dynamic Workers solve a specific problem: runtime execution of variable code. Applying them broadly adds complexity without purpose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right use cases are &lt;strong&gt;user-configurable transforms&lt;/strong&gt; and &lt;strong&gt;sandboxed execution of untrusted code&lt;/strong&gt; — places where the logic changes without redeploying, or where you need a hard security boundary between arbitrary code and your platform.&lt;/p&gt;

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

&lt;p&gt;I'm building a feed management UI in my authoring app — list subscriptions, toggle auto-posting, edit templates in a code editor. The API endpoints already exist; it's the frontend that needs work.&lt;/p&gt;

&lt;p&gt;Beyond that, the same Dynamic Worker pattern applies to syndication formatting (per-platform rules stored in D1, editable without redeploying) and AI content transforms (LLM generates the code, Dynamic Worker executes it, parent validates the output).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/cloudflare-dynamic-workers" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>javascript</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Who Moved My Settings? Automating Vendor Doc Drift Detection</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Mon, 23 Mar 2026 21:58:16 +0000</pubDate>
      <link>https://dev.to/rickcogley/who-moved-my-settings-automating-vendor-doc-drift-detection-2f0l</link>
      <guid>https://dev.to/rickcogley/who-moved-my-settings-automating-vendor-doc-drift-detection-2f0l</guid>
      <description>&lt;p&gt;Spencer Johnson's &lt;em&gt;Who Moved My Cheese?&lt;/em&gt; is about adapting when things change around you. In cloud security compliance, the cheese moves constantly. Microsoft renames a menu. Google moves a toggle to a different admin panel. Your carefully written implementation guide now points to a screen that no longer exists. Your client follows the old steps and hits a dead end.&lt;/p&gt;

&lt;p&gt;We built an automated pipeline that crawls vendor documentation weekly, compares it against our internal implementation guides, and flags meaningful drift: renamed UI paths, deprecated features, relocated settings. It runs on Cloudflare Workers with a Durable Workflow, uses Claude for semantic analysis, and costs about 50 cents a week at scale. Here's how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Documentation Drift
&lt;/h2&gt;

&lt;p&gt;We maintain implementation guides for security controls, such as "Enable MFA for Admin Accounts in Microsoft Entra" or "Configure Conditional Access Policies." Each guide has specific, click-by-click instructions referencing particular admin portal locations, feature names, and configuration options.&lt;/p&gt;

&lt;p&gt;Cloud vendors update their platforms constantly. Microsoft alone pushes changes to Entra ID (formerly Azure AD) multiple times a month. When they rename a feature or reorganize an admin panel, our guides become incorrect. We call this &lt;strong&gt;documentation drift&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before building this system, drift detection was entirely manual. A consultant would notice a discrepancy during a client engagement, report it, and someone would update the guide. The feedback loop was slow and reactive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: A Three-Gate Pipeline
&lt;/h2&gt;

&lt;p&gt;We wanted something cheap to run, resistant to false alarms, and automated. The solution is a three-gate pipeline where each gate filters out unnecessary work before reaching the next, more expensive step.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Gate 1: RSS Check (Free, Milliseconds)
&lt;/h3&gt;

&lt;p&gt;Microsoft Learn exposes an RSS endpoint that reports page modification dates. Before crawling anything, we check whether the vendor has even published changes since our last run. If the RSS timestamps haven't moved, we skip the entire source. This gate eliminates most checks in a typical week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 2: Content Hash (Cheap, Seconds)
&lt;/h3&gt;

&lt;p&gt;When RSS indicates something might have changed, we crawl the vendor page and compute a SHA-256 hash of the markdown content. If the hash matches what we stored last time, the page content hasn't actually changed. Maybe the RSS date shifted for editorial reasons, or the page got a cosmetic update. Skip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 3: AI Drift Analysis (API Cost, Seconds)
&lt;/h3&gt;

&lt;p&gt;Only when the content has genuinely changed do we send it to Claude for semantic comparison against our implementation guide. The AI isn't doing a text diff. It detects specific categories of drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI path changes&lt;/strong&gt;: "Settings &amp;gt; Security" became "Protection &amp;gt; Authentication"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature deprecation&lt;/strong&gt;: a feature we reference no longer exists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step reordering&lt;/strong&gt;: the vendor changed the sequence of configuration steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New prerequisites&lt;/strong&gt;: a step now requires something it didn't before&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting relocation&lt;/strong&gt;: a toggle moved from one admin panel to another&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It deliberately ignores minor wording changes, formatting differences, and additive features that don't affect existing instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Companion Worker?
&lt;/h2&gt;

&lt;p&gt;Our main application runs on SvelteKit with Cloudflare's adapter, which doesn't support Workflow classes or &lt;code&gt;scheduled&lt;/code&gt; handlers. So the crawler runs as a separate Worker that shares the same D1 database. Two Workers, one database, clean separation of concerns.&lt;/p&gt;

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

&lt;p&gt;The companion Worker uses Cloudflare Workflows for durability. Each doc source gets its own named steps. If a crawl job fails partway through, the workflow resumes from the last completed step rather than starting over. Durable sleep steps let the Worker wait for asynchronous crawl jobs without burning CPU time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with a Flaky API
&lt;/h2&gt;

&lt;p&gt;The Cloudflare Browser Rendering &lt;code&gt;/crawl&lt;/code&gt; endpoint is asynchronous and occasionally unreliable. Jobs sometimes error out immediately for no apparent reason. We handle this with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3 crawl attempts&lt;/strong&gt; with 20-second backoff between retries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;15-second stagger&lt;/strong&gt; between sources to avoid rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling with durable sleep&lt;/strong&gt;: the Worker sleeps (zero CPU cost) between poll checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This brought our success rate from roughly 50% to about 90%. The remaining failures are typically transient and resolve on the next weekly run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Drift Detection Looks Like
&lt;/h2&gt;

&lt;p&gt;When the system detects drift, it stores a bilingual summary (English and Japanese) with structured change details. A staff member reviews the flag and either updates the implementation guide or dismisses it as a false positive.&lt;/p&gt;

&lt;p&gt;Real drift we've caught so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft deprecated per-user MFA in favor of Conditional Access policies&lt;/li&gt;
&lt;li&gt;UI paths in the Entra admin center were reorganized&lt;/li&gt;
&lt;li&gt;A grace period for legacy authentication blocking was removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these would have eventually caused confusion during a client engagement. Instead, we caught them within a week of the vendor change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;For our current 10 doc sources (5 M365 controls × 2 languages), the weekly cost rounds to zero. At scale (say 120 sources covering Microsoft 365, Google Workspace, and Cloudflare) we estimate about $0.50 per week. Gate 1 and Gate 2 eliminate most of the expensive AI calls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Weekly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RSS checks (Gate 1)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser Rendering crawls&lt;/td&gt;
&lt;td&gt;~$0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude AI analysis (Gate 3)&lt;/td&gt;
&lt;td&gt;~$0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker compute + D1 storage&lt;/td&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total (~120 sources)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$0.50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;The current system covers Microsoft 365 identity controls. The pipeline is vendor-agnostic. Adding a new doc source is just a database row with a URL and crawl configuration. Next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expand coverage&lt;/strong&gt; to remaining M365 controls plus Google Workspace and Cloudflare&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the review UI&lt;/strong&gt; so staff can view diffs, approve updates, and track guide versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor-specific adapters&lt;/strong&gt; for Gate 1 (Google and Cloudflare have different changelog patterns than Microsoft's RSS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic guide updates&lt;/strong&gt; where the AI suggests specific text changes, not just flags&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Vendor documentation drift is a real operational risk for any team maintaining compliance guides. Catching it doesn't require a complex system. A pipeline that filters aggressively at each stage keeps both costs and false alarms low. Cloudflare's Worker platform, with Workflows for durability and Browser Rendering for crawling, turned out to be a natural fit for this kind of periodic, asynchronous inspection work.&lt;/p&gt;

&lt;p&gt;The code runs once a week, costs pocket change, and has already caught real drift that would have burned consultant time. That's a good trade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/automating-vendor-doc-drift-detection" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Enabling WebMCP Tools on my SvelteKit Migration Reference</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:12:51 +0000</pubDate>
      <link>https://dev.to/rickcogley/enabling-webmcp-tools-on-my-sveltekit-migration-reference-4p</link>
      <guid>https://dev.to/rickcogley/enabling-webmcp-tools-on-my-sveltekit-migration-reference-4p</guid>
      <description>&lt;p&gt;An AI agent asks: "What's the Svelte 5 equivalent of useEffect?" Today it has two options — scrape the page HTML and parse it, or fetch &lt;code&gt;api/data.json&lt;/code&gt; and filter 100+ items in context. Both work ok, but they both waste tokens. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebMCP&lt;/strong&gt; gives our AI agent a third option: call &lt;code&gt;get_svelte_equivalent({ framework: 'react', concept: 'Side Effects' })&lt;/code&gt; and get a structured answer directly. The data is the same. The interface is purpose-built for agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What WebMCP Is
&lt;/h2&gt;

&lt;p&gt;WebMCP is a &lt;a href="https://webmachinelearning.github.io/webmcp/" rel="noopener noreferrer"&gt;W3C Community Group Draft&lt;/a&gt; that adds &lt;code&gt;navigator.modelContext&lt;/code&gt; to browsers. It lets websites register &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; tools — the same protocol that powers Claude's tool use, Cursor's context, and other agent frameworks — directly in a webpage. An AI agent browsing the site can discover and call those tools without scraping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.google.com/chrome/canary/" rel="noopener noreferrer"&gt;Chrome Canary&lt;/a&gt; (146+) has WebMCP behind a flag, and &lt;strong&gt;Microsoft Edge&lt;/strong&gt; appears to support it out of the box — no flags or settings needed. For other browsers, &lt;a href="https://www.npmjs.com/package/@mcp-b/global" rel="noopener noreferrer"&gt;&lt;code&gt;@mcp-b/global&lt;/code&gt;&lt;/a&gt; from the &lt;a href="https://github.com/WebMCP-org/npm-packages" rel="noopener noreferrer"&gt;MCP-B project&lt;/a&gt; fills in, until native support ships. Don't confuse this with Anthropic's &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP server protocol&lt;/a&gt; — WebMCP is a &lt;em&gt;client-side&lt;/em&gt; browser API that happens to speak the same message format.&lt;/p&gt;

&lt;h2&gt;
  
  
  The App
&lt;/h2&gt;

&lt;p&gt;My site &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;svelte.cogley.jp&lt;/a&gt; is a migration reference showing how concepts from React, Vue, Angular, and now &lt;strong&gt;Svelte 4&lt;/strong&gt; map to &lt;a href="https://svelte.dev/docs/svelte/overview" rel="noopener noreferrer"&gt;Svelte 5&lt;/a&gt;. There are 120+ structured mappings across four categories: overview, syntax, architecture, and ecosystem. Each mapping has a concept name, source framework code, Svelte equivalent code, notes, doc links, and bilingual support (English/Japanese, well, because I can).&lt;/p&gt;

&lt;p&gt;The data already has machine interfaces: a JSON API at &lt;code&gt;/api/data.json&lt;/code&gt;, &lt;a href="https://dev.to/articles/markdown-for-agents"&gt;content negotiation via &lt;code&gt;Accept: text/markdown&lt;/code&gt;&lt;/a&gt;, and an &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; describing the site for crawlers. WebMCP adds another layer — typed tools that agents can discover and call in-page without fetching or parsing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading Strategy
&lt;/h2&gt;

&lt;p&gt;The polyfill weighs ~285KB. Most visitors are humans browsing migration cards, not AI agents calling tools — so shipping it in the initial bundle would be wasteful. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import" rel="noopener noreferrer"&gt;Dynamic import&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;language&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Force polyfill mode — Chrome Canary's native modelContext is&lt;/span&gt;
  &lt;span class="c1"&gt;// incomplete (missing listTools/registerTool), which crashes the&lt;/span&gt;
  &lt;span class="c1"&gt;// native adapter. Disable auto-init, wipe, then reinitialize.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;win&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__webModelContextOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;autoInitialize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cleanupWebModelContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initializeWebModelContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mcp-b/global&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;cleanupWebModelContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;modelContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;configurable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* non-configurable — already cleaned */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;initializeWebModelContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerMigrationTools&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/webmcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;registerMigrationTools&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vite code-splits the polyfill into its own chunk. The &lt;code&gt;__webModelContextOptions&lt;/code&gt; flag prevents the module's auto-initialization from crashing on browsers (like Chrome Canary) where the native &lt;code&gt;navigator.modelContext&lt;/code&gt; exists but is incomplete — missing &lt;code&gt;listTools()&lt;/code&gt; and &lt;code&gt;registerTool()&lt;/code&gt;. After wiping the incomplete native API, &lt;code&gt;initializeWebModelContext()&lt;/code&gt; installs the full polyfill. On Edge and browsers without a native API, &lt;code&gt;cleanupWebModelContext()&lt;/code&gt; is a no-op and the polyfill installs normally.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;registerMigrationTools()&lt;/code&gt; import is dynamic too, keeping tool definitions out of both the server bundle and the initial page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the Tools
&lt;/h2&gt;

&lt;p&gt;Six read-only tools answer questions that AI agents might want to do, like "what's the Svelte equivalent of X?"&lt;/p&gt;

&lt;h3&gt;
  
  
  search_mappings
&lt;/h3&gt;

&lt;p&gt;Full-text search across concepts, code, and notes. Same filter logic as the UI's search box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_mappings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search migration mappings by keyword...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search keyword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;overview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;syntax&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;architecture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecosystem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ja&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readOnlyHint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter pool by framework/category, then substring-match on query&lt;/span&gt;
    &lt;span class="c1"&gt;// Return structured JSON with framework/category context per match&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent searching for "useState" gets React entries; searching for "routing" gets results across all four frameworks. The optional &lt;code&gt;framework&lt;/code&gt; and &lt;code&gt;category&lt;/code&gt; filters let agents narrow results when they already know what they're looking for.&lt;/p&gt;

&lt;h3&gt;
  
  
  get_svelte_equivalent
&lt;/h3&gt;

&lt;p&gt;Direct lookup: give it a framework and concept name, get the best match. Uses exact match first, then substring fallback. If the agent asks for &lt;code&gt;{ framework: 'react', concept: 'Reactive State' }&lt;/code&gt;, it gets the &lt;code&gt;$state&lt;/code&gt; mapping with full code examples and notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  list_ecosystem_gaps
&lt;/h3&gt;

&lt;p&gt;Items where the &lt;code&gt;to&lt;/code&gt; field contains "No equivalent" or "⚠️". This is the kind of query that's awkward to express via JSON filtering but natural as a tool call: "What can't I do in Svelte that I can do in React?"&lt;/p&gt;

&lt;h3&gt;
  
  
  The Other Three
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;get_site_info&lt;/strong&gt; — overview: frameworks, categories, total count, API URLs. The "hello world" tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;get_changelog&lt;/strong&gt; — recent updates, bilingual, with configurable limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compare_frameworks&lt;/strong&gt; — side-by-side: how React, Vue, Angular, and Svelte 4 each handle the same concept. An agent comparing "Reactive State" gets &lt;code&gt;useState&lt;/code&gt; vs &lt;code&gt;ref()&lt;/code&gt; vs &lt;code&gt;signal()&lt;/code&gt; vs &lt;code&gt;let count = 0&lt;/code&gt; → &lt;code&gt;$state&lt;/code&gt; in one call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Registration Module
&lt;/h2&gt;

&lt;p&gt;The entire module is ~180 lines of TypeScript. Shared helpers handle filtering and matching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MappingItem&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;MappingItem&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concept_ja&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notes_ja&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checklist&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
      &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checklist_ja&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same logic that drives the &lt;code&gt;$derived&lt;/code&gt; block in &lt;code&gt;+page.svelte&lt;/code&gt;. The UI and the tools search identically — one filter function, two consumers.&lt;/p&gt;

&lt;p&gt;Every tool returns MCP's standard content format (&lt;code&gt;{ content: [{ type: 'text', text: JSON.stringify(...) }] }&lt;/code&gt;) and carries &lt;code&gt;annotations: { readOnlyHint: true }&lt;/code&gt;. Query only, no mutations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Edge&lt;/strong&gt; works out of the box. &lt;strong&gt;Chrome Canary&lt;/strong&gt; needs the &lt;a href="https://webmachinelearning.github.io/webmcp/#browser-support" rel="noopener noreferrer"&gt;WebMCP flag&lt;/a&gt; (&lt;code&gt;chrome://flags&lt;/code&gt; → "WebMCP for testing"). Alternatively, install the &lt;a href="https://github.com/WebMCP-org/npm-packages" rel="noopener noreferrer"&gt;MCP-B extension&lt;/a&gt;. Open the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// List tools&lt;/span&gt;
&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listTools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// → Array of 6 tools with names, descriptions, input schemas&lt;/span&gt;

&lt;span class="c1"&gt;// Search for useState&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_mappings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Direct lookup&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_svelte_equivalent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reactive State&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Compare across frameworks&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;callTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compare_frameworks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reactive State&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In browsers without native support, the polyfill loads silently with no visible UI change and no console errors. Here it is in Chrome Canary, but I tested in Edge too and it worked without any special settings: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd901xxpst86v2lizwlrx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd901xxpst86v2lizwlrx.png" alt="Computer screen displaying Svelte 5 migration guide with green and blue code and text." width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating llms.txt
&lt;/h2&gt;

&lt;p&gt;I added a WebMCP section to &lt;code&gt;llms.txt&lt;/code&gt; listing all six tools with their parameters. An agent that discovers the site via &lt;code&gt;llms.txt&lt;/code&gt; now knows it can call tools directly — it doesn't have to guess whether the site supports WebMCP or fall back to fetching JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update: Svelte 4 → 5 Upgrade Data
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;22 Feb 2026&lt;/em&gt; — I added &lt;strong&gt;Svelte 4&lt;/strong&gt; as a fourth source framework. This one's different from the other three — it's not a cross-framework migration, it's an &lt;em&gt;upgrade&lt;/em&gt; guide for developers already on Svelte who need to move from v4 to v5 (runes, snippets, new event syntax, stores → reactive classes).&lt;/p&gt;

&lt;p&gt;The data includes 25 new mappings: 4 overview items, 12 syntax mappings, 5 architecture patterns, and 4 ecosystem entries. The overview category has a new card type — a &lt;strong&gt;ChecklistCard&lt;/strong&gt; — that renders a step-by-step migration checklist with numbered steps instead of the usual two-column code comparison. The checklist covers everything from &lt;code&gt;npm install svelte@5&lt;/code&gt; through running &lt;code&gt;sv migrate&lt;/code&gt; to testing &lt;code&gt;$$props&lt;/code&gt; edge cases.&lt;/p&gt;

&lt;p&gt;A new &lt;code&gt;checklist&lt;/code&gt; field on &lt;code&gt;MappingItem&lt;/code&gt; holds the step arrays, with &lt;code&gt;checklist_ja&lt;/code&gt; for Japanese translations. All four machine-readable interfaces were updated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebMCP tools&lt;/strong&gt; — &lt;code&gt;search_mappings&lt;/code&gt; and &lt;code&gt;get_svelte_equivalent&lt;/code&gt; now accept &lt;code&gt;framework: 'svelte4'&lt;/code&gt;; search covers checklist text; &lt;code&gt;localizeItem&lt;/code&gt; returns the checklist array&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/strong&gt; — Svelte 4 section renders checklist steps as numbered lists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/strong&gt; — framework list and tool descriptions updated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;data.json&lt;/code&gt;&lt;/strong&gt; — includes the full svelte4 data structure automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent can now do things like &lt;code&gt;get_svelte_equivalent({ framework: 'svelte4', concept: 'Props' })&lt;/code&gt; to learn that &lt;code&gt;export let prop&lt;/code&gt; → &lt;code&gt;let { prop } = $props()&lt;/code&gt;, or &lt;code&gt;search_mappings({ query: 'stores', framework: 'svelte4' })&lt;/code&gt; to find the stores → reactive classes migration path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Cost
&lt;/h2&gt;

&lt;p&gt;The registration module is ~200 lines of TypeScript, with six tools. Zero bytes added to the initial page bundle of &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;https://svelte.cogley.jp&lt;/a&gt; because everything loads dynamically — the polyfill chunk (429KB) only fires if the browser doesn't have native support yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration data was already structured and queryable. Now — with four frameworks and 120+ mappings including the Svelte 4 → 5 upgrade path — the site has an interface that AI agents can use without scraping.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/enabling-webmcp-tools-on-my-sveltekit-migration-reference" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>svelte</category>
    </item>
    <item>
      <title>Markdown for Agents on SvelteKit + Cloudflare Workers</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:12:42 +0000</pubDate>
      <link>https://dev.to/rickcogley/markdown-for-agents-on-sveltekit-cloudflare-workers-2ae4</link>
      <guid>https://dev.to/rickcogley/markdown-for-agents-on-sveltekit-cloudflare-workers-2ae4</guid>
      <description>&lt;p&gt;The AI crawl-and-summarize wave has landed. Google's Gemini, OpenAI's GPT, Anthropic's Claude, Perplexity — they're all hitting your site. If you serve them HTML, they waste tokens parsing `` soup. If you serve them markdown, they get clean context immediately. &lt;/p&gt;

&lt;p&gt;Cloudflare published a &lt;a href="https://blog.cloudflare.com/markdown-for-bots/" rel="noopener noreferrer"&gt;guide to serving markdown for AI agents&lt;/a&gt; using Transform Rules and their AI gateway. Michael Wolson then wrote an excellent &lt;a href="https://mwolson.org/blog/2026-02-14-markdown-for-agents-on-cloudflare-free-plan/" rel="noopener noreferrer"&gt;adaptation for the free plan&lt;/a&gt; using Transform Rules to set headers that content-negotiate at the edge.&lt;/p&gt;

&lt;p&gt;I run SvelteKit on Cloudflare Workers. My situation is different — and simpler. Here's how I implemented it and why no Transform Rules were necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Transform Rules on Workers
&lt;/h2&gt;

&lt;p&gt;Wolson's approach is clever: use a Cloudflare Transform Rule to inject a custom header based on the &lt;code&gt;Accept&lt;/code&gt; header, then check that header in your application to decide the response format. This solves a real problem for static sites: CDN caching. Without differentiated cache keys, a cached HTML response gets served to a markdown-requesting bot, or vice versa.&lt;/p&gt;

&lt;p&gt;Workers don't have this problem. Every request executes the Worker — there's no CDN cache layer sitting in front of dynamic routes on Pages Functions. The Worker sees the raw &lt;code&gt;Accept&lt;/code&gt; header directly and can branch on it before any rendering happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The implementation hooks into SvelteKit's server hooks — the middleware layer that runs before any route handler.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`mermaid&lt;br&gt;
flowchart TD&lt;br&gt;
    A[Incoming Request] --&amp;gt; B{Accept: text/markdown?&lt;br&gt;
or ?format=md}&lt;br&gt;
    B --&amp;gt;|Yes| C[handleMarkdownRequest]&lt;br&gt;
    C --&amp;gt; D{Route matched?}&lt;br&gt;
    D --&amp;gt;|Yes| E[Fetch API data via&lt;br&gt;
Service Binding]&lt;br&gt;
    E --&amp;gt; F[Format as markdown]&lt;br&gt;
    F --&amp;gt; G[Return Response&lt;br&gt;
text/markdown + headers]&lt;br&gt;
    D --&amp;gt;|No| H[Fall through to SSR]&lt;br&gt;
    B --&amp;gt;|No| H&lt;br&gt;
    H --&amp;gt; I[Normal HTML Response]&lt;br&gt;
    G --&amp;gt; J[Add security headers]&lt;br&gt;
    I --&amp;gt; J&lt;br&gt;
    J --&amp;gt; K[Response to client]&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The key insight: all my content already lives in the API with raw markdown fields. Posts have &lt;code&gt;content&lt;/code&gt; (markdown). Articles have &lt;code&gt;content&lt;/code&gt; (markdown). Pages have &lt;code&gt;content&lt;/code&gt; (markdown). No HTML-to-markdown conversion needed — I just skip the rendering step entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection
&lt;/h2&gt;

&lt;p&gt;Two signals trigger markdown responses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Accept: text/markdown&lt;/code&gt; header (per the emerging convention)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;?format=md&lt;/code&gt; query parameter (for easy browser testing)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;`typescript&lt;br&gt;
function wantsMarkdown(request: Request): boolean {&lt;br&gt;
  if (request.headers.get('accept')?.includes('text/markdown')) return true;&lt;br&gt;
  const url = new URL(request.url);&lt;br&gt;
  return url.searchParams.get('format') === 'md';&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hook
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;hooks.server.ts&lt;/code&gt;, the markdown check runs after legacy redirects but before &lt;code&gt;resolve(event)&lt;/code&gt; — meaning SvelteKit never renders any Svelte components for markdown requests:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`typescript&lt;br&gt;
if (wantsMarkdown(event.request)) {&lt;br&gt;
  const mdResponse = await handleMarkdownRequest(event);&lt;br&gt;
  if (mdResponse) {&lt;br&gt;
    // Security headers still apply&lt;br&gt;
    mdResponse.headers.set('X-Content-Type-Options', 'nosniff');&lt;br&gt;
    mdResponse.headers.set('X-Frame-Options', 'DENY');&lt;br&gt;
    // ...&lt;br&gt;
    return mdResponse;&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
// Unmatched routes fall through to normal HTML&lt;br&gt;
const response = await resolve(event);&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Routes without a markdown handler (like &lt;code&gt;/security&lt;/code&gt; or &lt;code&gt;/tweet-archive&lt;/code&gt;) fall through to normal SSR. No 406 errors, no broken pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route Handlers
&lt;/h2&gt;

&lt;p&gt;Each route is a regex pattern matched against the pathname, paired with a handler that fetches from the API and formats the response:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Data Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recent posts + presence status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/now&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full /now page data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/posts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Published micro posts (last 50)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/posts/:slug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single post — raw &lt;code&gt;content&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/articles&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Published article list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/articles/:slug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single article — raw &lt;code&gt;content&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/pages/:slug&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single page — raw &lt;code&gt;content&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Individual content pages return the markdown as-is with minimal front matter:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;markdown&lt;/p&gt;

&lt;h1&gt;
  
  
  Article Title
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Published:&lt;/strong&gt; 2026-02-18 · &lt;strong&gt;Stream:&lt;/strong&gt; tech&lt;br&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://cogley.jp/articles/some-slug" rel="noopener noreferrer"&gt;https://cogley.jp/articles/some-slug&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;[raw markdown content from API]&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;List pages provide a structured index with truncated previews and URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Dump: /llms-full.txt
&lt;/h2&gt;

&lt;p&gt;For agents that want everything at once, &lt;code&gt;/llms-full.txt&lt;/code&gt; returns all articles, all pages, the now page, and the 50 most recent posts stitched into a single markdown document. This endpoint always returns markdown regardless of the &lt;code&gt;Accept&lt;/code&gt; header — it's explicitly for machine consumption.&lt;/p&gt;

&lt;p&gt;Combined with the existing &lt;code&gt;/llms.txt&lt;/code&gt; (which describes the site structure and available sections), agents have a complete discovery path:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`mermaid&lt;br&gt;
flowchart LR&lt;br&gt;
    A[Agent discovers site] --&amp;gt; B[GET /llms.txt]&lt;br&gt;
    B --&amp;gt; C[Understands site structure]&lt;br&gt;
    C --&amp;gt; D{Need everything?}&lt;br&gt;
    D --&amp;gt;|Yes| E[GET /llms-full.txt]&lt;br&gt;
    D --&amp;gt;|No| F[GET /articles/specific-slug&lt;br&gt;
Accept: text/markdown]&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Estimation
&lt;/h2&gt;

&lt;p&gt;Every markdown response includes an &lt;code&gt;x-markdown-tokens&lt;/code&gt; header with a rough token count estimate (&lt;code&gt;content.length / 4&lt;/code&gt;). It's not precise — real tokenization varies by model — but it gives agents a quick way to gauge response size before processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Approach Works for Workers
&lt;/h2&gt;

&lt;p&gt;The Cloudflare blog and Wolson's approach both solve a caching problem that Workers don't have:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Static/CDN Sites&lt;/th&gt;
&lt;th&gt;Workers/Pages Functions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CDN cache collision&lt;/td&gt;
&lt;td&gt;Real problem — same URL, different Accept&lt;/td&gt;
&lt;td&gt;Non-issue — Worker always executes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transform Rules needed&lt;/td&gt;
&lt;td&gt;Yes, to differentiate cache keys&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Vary: Accept&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Insufficient alone (CDN ignores it)&lt;/td&gt;
&lt;td&gt;Works correctly (no CDN layer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Edge rules + origin logic&lt;/td&gt;
&lt;td&gt;Just origin logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Setting &lt;code&gt;Vary: Accept&lt;/code&gt; is still good practice for HTTP correctness, but it's not load-bearing the way it would be on a CDN-cached static site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serving the Profile Site Too
&lt;/h2&gt;

&lt;p&gt;My profile site at rick.cogley.jp uses the same pattern. The profile sections only store &lt;code&gt;content_html&lt;/code&gt; (not raw markdown), so the handler uses &lt;code&gt;stripHtml()&lt;/code&gt; to extract clean text. Not as rich as raw markdown, but vastly better than HTML tags for an AI agent trying to understand who I am.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing It
&lt;/h2&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Markdown via Accept header
&lt;/h1&gt;

&lt;p&gt;curl -s -H "Accept: text/markdown" &lt;a href="https://cogley.jp/now" rel="noopener noreferrer"&gt;https://cogley.jp/now&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Markdown via query param
&lt;/h1&gt;

&lt;p&gt;curl -s "&lt;a href="https://cogley.jp/now?format=md" rel="noopener noreferrer"&gt;https://cogley.jp/now?format=md&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Check headers
&lt;/h1&gt;

&lt;p&gt;curl -sI -H "Accept: text/markdown" &lt;a href="https://cogley.jp/posts" rel="noopener noreferrer"&gt;https://cogley.jp/posts&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Full site dump
&lt;/h1&gt;

&lt;p&gt;curl -s &lt;a href="https://cogley.jp/llms-full.txt" rel="noopener noreferrer"&gt;https://cogley.jp/llms-full.txt&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Normal HTML (no markdown signal)
&lt;/h1&gt;

&lt;p&gt;curl -s &lt;a href="https://cogley.jp/now" rel="noopener noreferrer"&gt;https://cogley.jp/now&lt;/a&gt;&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I were building this from scratch, I'd store all content as markdown and render HTML on demand — which is essentially what this site already does. The &lt;code&gt;/api&lt;/code&gt; already has markdown fields everywhere because that's what the editor produces. The HTML rendering happens in SvelteKit route handlers using &lt;code&gt;marked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For sites where content is HTML-first (CMS output, rich text editors), you'd need an HTML-to-markdown conversion step. Libraries like &lt;code&gt;turndown&lt;/code&gt; handle this, but the output won't be as clean as source markdown. If you're designing a new system, store the markdown.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/markdown-for-bots/" rel="noopener noreferrer"&gt;Cloudflare: Markdown for bots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mwolson.org/blog/2026-02-14-markdown-for-agents-on-cloudflare-free-plan/" rel="noopener noreferrer"&gt;mwolson.org: Markdown for agents on Cloudflare free plan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt specification&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/markdown-for-agents-on-sveltekit-cloudflare-workers" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>svelte</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Migrate to Svelte 5 — An Interactive Reference for Framework Refugees</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:12:29 +0000</pubDate>
      <link>https://dev.to/rickcogley/migrate-to-svelte-5-an-interactive-reference-for-framework-refugees-1g7k</link>
      <guid>https://dev.to/rickcogley/migrate-to-svelte-5-an-interactive-reference-for-framework-refugees-1g7k</guid>
      <description>&lt;p&gt;Do you feel the same? You've been working in React (or Vue, or Angular) for years, shipping production code, wrestling with dependency arrays, wrapping vanilla JS libraries in framework-specific adapters, and wondering why your &lt;code&gt;node_modules&lt;/code&gt; weighs more than your actual appl. Then you hear about Svelte 5 and its runes — &lt;code&gt;$state&lt;/code&gt;, &lt;code&gt;$derived&lt;/code&gt;, &lt;code&gt;$effect&lt;/code&gt; — and something clicks. You want to try it, but the migration path feels pretty rocky. &lt;/p&gt;

&lt;p&gt;That's the gap &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;my new site&lt;/a&gt; is designed to fill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's svelte.cogley.jp
&lt;/h2&gt;

&lt;p&gt;It's an interactive, side-by-side reference that maps concepts — not just syntax — from React, Vue, and Angular to Svelte 5 and SvelteKit. It's kind of a "Rosetta Stone" for frontend frameworks: you pick your source language (React, Vue, or Angular), choose a category (overview, syntax, architecture, or ecosystem), and see exactly how each concept translates. I've been using Svelte so it assumes you do, too.&lt;/p&gt;

&lt;p&gt;Every mapping includes code examples on both sides, with explanatory notes that capture the &lt;em&gt;why&lt;/em&gt;, not just the &lt;em&gt;what&lt;/em&gt;. The site also tries to flag ecosystem gaps honestly — places where Svelte doesn't have a mature equivalent yet, like native mobile — so you're making informed decisions rather than discovering surprises mid-migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four lenses
&lt;/h2&gt;

&lt;p&gt;The reference is organized into four categories, each designed around a different kind of question a migrating developer asks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt; covers philosophy, migration strategy, and the fundamental architectural differences. This is where you learn that Svelte is a compiler (not a runtime), that SvelteKit is to Svelte what Next.js is to React, and that incremental migration is perfectly viable — you don't need a Big Rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syntax&lt;/strong&gt; is the phrasebook. &lt;code&gt;useState&lt;/code&gt; becomes &lt;code&gt;$state&lt;/code&gt;. &lt;code&gt;useMemo&lt;/code&gt; becomes &lt;code&gt;$derived&lt;/code&gt;. Vue's &lt;code&gt;ref()&lt;/code&gt; and &lt;code&gt;.value&lt;/code&gt; unwrapping disappears entirely. Angular's &lt;code&gt;@Input()&lt;/code&gt; decorators become plain destructured &lt;code&gt;$props()&lt;/code&gt;. Each card shows the before and after, with some notes explaining the conceptual difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt; maps the structural patterns: file-based routing, server data loading, form handling, middleware, environment variables. SvelteKit's &lt;code&gt;+page.server.ts&lt;/code&gt; / &lt;code&gt;+page.svelte&lt;/code&gt; convention is explained relative to Next.js pages, Nuxt's &lt;code&gt;useFetch&lt;/code&gt;, and Angular's route guards and services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ecosystem&lt;/strong&gt; is the library mapping table. State management, data fetching, UI components, auth, animation, i18n, testing, charts — each row tells you the Svelte equivalent and assesses maturity. Gaps are flagged visually with badge counts, so you can see at a glance how many holes exist for a given framework. Some answers are satisfying (shadcn-svelte is a near 1:1 port of shadcn/ui), and some are frank (there is no React Native equivalent for Svelte, at least yet).&lt;/p&gt;

&lt;h2&gt;
  
  
  Bilingual
&lt;/h2&gt;

&lt;p&gt;The entire site works in English and Japanese. Language is auto-detected from your browser's &lt;code&gt;Accept-Language&lt;/code&gt; header on first visit, and you can toggle between EN/JA at any time. All concept names, explanatory notes, and UI chrome are fully translated. The site also supports dark and light themes, persisted across visits. My personal site is mostly English but here's a brief intro in Japanese:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;日本語の概要:&lt;/strong&gt; &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;svelte.cogley.jp&lt;/a&gt; は、React・Vue・AngularからSvelte 5とSvelteKitへの移行を支援するインタラクティブリファレンスサイトです。構文だけでなく、アーキテクチャやエコシステムの概念を並べて比較でき、日本語と英語の両方で利用可能です。ブラウザの言語設定を自動検出し、ダーク/ライトテーマにも対応。SvelteKit + Cloudflare Workersで構築されており、ターゲットフレームワーク自体で作られた「ドッグフーディング」サイトでもあります。&lt;/p&gt;

&lt;h2&gt;
  
  
  Dogfooding all the way down
&lt;/h2&gt;

&lt;p&gt;The site itself is built with SvelteKit, deployed as a Cloudflare Worker. It uses Svelte 5 runes (&lt;code&gt;$state&lt;/code&gt;, &lt;code&gt;$derived&lt;/code&gt;, &lt;code&gt;$effect&lt;/code&gt;), scoped CSS (no Tailwind), server-side language detection via &lt;code&gt;hooks.server.ts&lt;/code&gt;, and Phosphor icons. There's no database and no tracking — all content lives in a single TypeScript data file. It's a working example of the patterns it teaches.&lt;/p&gt;

&lt;p&gt;The font pairing is &lt;a href="https://fonts.google.com/specimen/Murecho" rel="noopener noreferrer"&gt;Murecho&lt;/a&gt; for body text (a bilingual-friendly Google Font) and &lt;a href="https://monaspace.githubnext.com/" rel="noopener noreferrer"&gt;Monaspace Krypton&lt;/a&gt; for code blocks (self-hosted). Both choices are intentional: Murecho handles Japanese and Latin text gracefully at the same weight, and Monaspace's texture healing makes code comparisons easier to scan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just read the Svelte docs?
&lt;/h2&gt;

&lt;p&gt;The official Svelte docs are excellent — for Svelte. But if you're coming from React, you don't think in Svelte terms yet. You think in &lt;code&gt;useEffect&lt;/code&gt; and &lt;code&gt;useContext&lt;/code&gt; and JSX ternaries. You need a translator that speaks your source language, maps it to the destination, and explains the differences in context. That's what this site tries to do.&lt;/p&gt;

&lt;p&gt;Migration docs also tend to go stale fast. Because all the content in this reference lives in one data file (&lt;code&gt;src/lib/data.ts&lt;/code&gt;), updates are a single-file edit, and the deployment is automated via GitHub Actions. It's designed to stay current as Svelte 5 evolves.&lt;/p&gt;

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

&lt;p&gt;Visit &lt;a href="https://svelte.cogley.jp" rel="noopener noreferrer"&gt;svelte.cogley.jp&lt;/a&gt;, pick your framework, and start exploring. If you find a mapping that's missing or wrong, please make a comment on this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latest: Auto-Updated Project Showcase
&lt;/h2&gt;

&lt;p&gt;The site now exposes a public changelog API at &lt;a href="https://svelte.cogley.jp/api/changelog.json" rel="noopener noreferrer"&gt;&lt;code&gt;/api/changelog.json&lt;/code&gt;&lt;/a&gt;, which returns the latest update date and change summary in a machine-readable format. This powers a new "Projects" section on&lt;br&gt;
  &lt;a href="https://cogley.jp/now" rel="noopener noreferrer"&gt;cogley.jp/now&lt;/a&gt; and a "Side Projects" card on &lt;a href="https://rick.cogley.jp" rel="noopener noreferrer"&gt;rick.cogley.jp&lt;/a&gt;, both of which automatically display the most recent update without any manual edits. The changelog data is fetched server-side at render time, so the latest update date stays current as I push changes to the migration reference.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;em&gt;Built by &lt;a href="https://rick.cogley.jp" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; in Tokyo. SvelteKit + Cloudflare Workers. No tracking, no ads, just framework translation, yes.&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/migrate-to-svelte-5-interactive-reference" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tech</category>
      <category>svelte</category>
    </item>
    <item>
      <title>The AI vs Creator Dilemma</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:12:17 +0000</pubDate>
      <link>https://dev.to/rickcogley/the-ai-vs-creator-dilemma-3k6a</link>
      <guid>https://dev.to/rickcogley/the-ai-vs-creator-dilemma-3k6a</guid>
      <description>&lt;p&gt;The web runs on a fundamental bargain: creators publish content, readers consume it, and somewhere in between, value flows back—through advertising, subscriptions, or traffic that converts to business opportunities. AI has upended this equation in ways we're only beginning to understand. &lt;/p&gt;

&lt;p&gt;This week brought a crystallizing and sobering moment: Tailwind CSS laid off 75% of its engineering team despite being more popular than ever. The framework powers the design of sites for NASA, Shopify, and countless others. We hear revenue dropped 80%, docs traffic fell 40%. The driver here wasn't quality or competition, but rather AI coding assistants answering questions without anyone visiting the docs.&lt;/p&gt;

&lt;p&gt;If your business model depends on people visiting your content, the Tailwind situation should be a wake-up call. This is a preview of what's coming. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Value Chain Has Broken
&lt;/h2&gt;

&lt;p&gt;Think of how things used to work. A front end developer has a question about Tailwind utility classes as they are building their site. They search, land on tailwindcss.com, find an answer, and maybe notice Tailwind Plus and decide to buy it. We did. The documentation itself has been both the product support and the marketing funnel.&lt;/p&gt;

&lt;p&gt;Now? The same developer probaby asks Cursor, Gemini or Claude Code, getting an answer instantaneously without ever even leaving their coding editor. The question gets answered, Tailwind gets used, but Tailwind Labs sees no traffic, no views of paid product landing pages, and therefore no business conversion opportunities. &lt;/p&gt;

&lt;p&gt;The huge and bitter irony here is that the AI models that the coding assistants use were &lt;em&gt;trained on&lt;/em&gt; this same documentation. And the value flowed in one direction only. &lt;/p&gt;

&lt;h2&gt;
  
  
  Two Responses to the Same Problem
&lt;/h2&gt;

&lt;p&gt;The divergence in how popular projects are responding reveals a genuine philosophical split.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Embrace Strategy: Svelte&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Svelte team appears to have taken a different path. As part of Advent of Svelte 2024, they introduced comprehensive AI support through standardized &lt;code&gt;llms.txt&lt;/code&gt; documentation files. I heard on a podcast that their docs now include multiple formats — full for comprehensive access, and even distilled versions optimized for use in an AI agent, with a limited "context window".&lt;/p&gt;

&lt;p&gt;Svelte is also helping devs develop better Svelte 5 code, via a dedicated &lt;a href="https://svelte.dev/docs/mcp/overview" rel="noopener noreferrer"&gt;MCP (Model Context Protocol) server&lt;/a&gt; that can be leveraged by AI coding assistants. The MCP provides tools for listing documentation sections, retrieving relevant docs on demand, and even a &lt;code&gt;svelte-autofixer&lt;/code&gt; that analyzes generated code for issues and best practices. For example, you can easily add it to Claude Code with a single command: &lt;code&gt;npx sv add mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This represents the logical endpoint of the "embrace" philosophy: don't just make your documentation available to AI—build infrastructure that makes AI &lt;em&gt;better at your ecosystem&lt;/em&gt;. It is a feedback loop: developers using Claude Code or Codex to scaffold Svelte projects will have better experiences because of the MCP, leading to more Svelte adoption.&lt;/p&gt;

&lt;p&gt;Whether this translates to sustainable revenue for the Svelte team remains an open question. But so far, it's a fundamentally different bet than Tailwind is making.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Protect Strategy: Tailwind&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a community member submitted a pull request adding &lt;code&gt;llms.txt&lt;/code&gt; support to Tailwind's documentation site, founder Adam Wathan rejected it. Not because he opposes the feature in principle, but because he's trying to save his business first.&lt;/p&gt;

&lt;p&gt;His explanation was raw:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The reality is that 75% of the people on our engineering team lost their jobs here yesterday because of the brutal impact AI has had on our business. And making it easier for LLMs to read our docs just means less traffic to our docs which means less people learning about our paid products and the business being even less sustainable."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can see that the discussion thread got a bit heated - some called him short-sighted, others pointed out the irony: AI companies worth billions built products trained on freely available documentation, then redirected the value that documentation used to capture. Well &lt;em&gt;of course they did&lt;/em&gt;, and while both sides are rational I think, neither is entirely right. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Attribution Gap
&lt;/h2&gt;

&lt;p&gt;Here's where I can't help but get a bit cynical. Some AI providers do indeed cite their sources. For example, Anthropic includes citations in its responses when Claude uses web search, linking back to the original content. And while I think that's a step in the right direction, the citation alone doesn't solve the problem. &lt;/p&gt;

&lt;p&gt;A link in a footnote doesn't generate the traffic, engagement, or conversion opportunities that someone actually reading your content does. It's better than nothing I guess, but people are getting everything neatly packaged by AI, so there is necessarily going to be less focus on where that information came from. But a link is somehow not the same as what was lost.&lt;/p&gt;

&lt;p&gt;The core issue is that content has become training data and retrieval source material without the consent, compensation, or even acknowledgment of the people who created it. Your blog post, your documentation, your carefully crafted tutorial, there it goes, getting fed into a model that generates revenue for someone else. Then again, maybe it's the "same as it ever was", thinking of Google's ad revenue model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare's Interesting Bet
&lt;/h2&gt;

&lt;p&gt;Cloudflare has been building toward something potentially significant. In September 2024, they announced AI Audit—tools to see how AI bots access your content and make informed decisions about blocking or allowing them. On July 1, 2025, their "Content Independence Day", they rolled out "Pay per Crawl" in private beta, a marketplace where content creators can set prices for AI crawlers to access their material. They also became the first major infrastructure provider to block AI crawlers &lt;em&gt;by default&lt;/em&gt; on new domains.&lt;/p&gt;

&lt;p&gt;The technical mechanism uses HTTP 402 (Payment Required), a status code that's been defined since HTTP 1.1 (that's a long time ago) but rarely used. When an AI crawler requests content, publishers can return either the content with 200 OK, or a 402 with pricing information. Cloudflare acts as the payment intermediary. Major publishers including Condé Nast, TIME, The Associated Press, and Fortune have signed on.&lt;/p&gt;

&lt;p&gt;It's clever, but I'm skeptical it will actually work. Consider the economics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI companies are already operating at significant losses&lt;/li&gt;
&lt;li&gt;Investors expect returns, not new expense categories&lt;/li&gt;
&lt;li&gt;The largest AI players have already scraped most of the open web&lt;/li&gt;
&lt;li&gt;Future training may shift toward synthetic data and licensed premium sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The power asymmetry is stark: a handful of well-funded AI companies have enormous leverage, while millions of individual creators have almost none. Cloudflare's marketplace assumes AI companies will voluntarily participate in paying for content they can often get just by scraping, anyway. That's a bad bet on goodwill from in-the-red organizations whose business models require a zero to extremely low content cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Stakes
&lt;/h2&gt;

&lt;p&gt;What happens if we get this wrong?&lt;/p&gt;

&lt;p&gt;The optimistic case: A sustainable model emerges. Content creators get compensated when AI uses their work. Quality content continues to be produced because there's economic incentive to produce it. The AI ecosystem and the content ecosystem achieve symbiosis.&lt;/p&gt;

&lt;p&gt;The pessimistic case: Value extraction continues until the sources dry up. Independent creators stop publishing detailed technical content because there's no return. Documentation becomes shallow and defensive. AI models eventually degrade because the content they depend on stops being created.&lt;/p&gt;

&lt;p&gt;We've seen this pattern before. Every ad-supported content model eventually collapsed into a race to the bottom—clickbait, SEO gaming, engagement farming. The question is whether AI will accelerate that collapse or create pressure for something better.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Personal Choice: Selective Participation
&lt;/h2&gt;

&lt;p&gt;For my own content, I've decided to participate selectively. I'll use Cloudflare's tools to block AI crawlers that don't cite sources. For crawlers that do attribute (like the ones that power Claude's web search with proper citations) I'm willing to have my content accessible to AI.&lt;/p&gt;

&lt;p&gt;This isn't a principled, really, it's just pragmatic self-defense. If my articles are going to be used, I want at least the acknowledgment of where that information came from. A citation that might lead someone back to the original source. A thin thread connecting the AI's answer to the human who wrote it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;llms.txt&lt;/code&gt; standard represents something interesting: creators proactively optimizing for AI consumption rather than just having their content scraped. But until there's real compensation, I'm not interested in making extraction more efficient for systems that give nothing back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Would Actually Help
&lt;/h2&gt;

&lt;p&gt;If I could design it, this is what I think a better system might look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mandatory attribution with real weight&lt;/strong&gt;: Not just a footnote, but prominent source linking in AI responses. Make the citation as visible as the answer itself. This is a decision about UX that AI companies can make, keeping the content's origin front and center.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Revenue sharing based on actual usage&lt;/strong&gt;: Track when specific content informs AI responses. Pay creators proportionally. Perplexity has started doing this with their Publisher Program, though the amounts are modest.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opt-in training with compensation&lt;/strong&gt;: Clear consent mechanisms for whether content can be used for training vs. just retrieval, with different compensation structures for each.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Crawler transparency&lt;/strong&gt;: Standardized identification of AI crawlers, their intended use (training vs. retrieval), and their citation policies. Let creators make informed decisions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small creator access to deals&lt;/strong&gt;: Right now, licensing deals go to News Corp and the New York Times. The long tail of creators—the millions of blog posts and tutorials and documentation pages that AI actually depends on—get nothing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Inevitable Adaptation
&lt;/h2&gt;

&lt;p&gt;The web is going to change. That's not a prediction; it's already happening. The question is whether that change serves creators, AI companies, or both.&lt;/p&gt;

&lt;p&gt;I'm not anti-AI. I use Claude extensively for my own work and am integrating it and other generative AI into my firm eSolia's ops. But I want to participate in an ecosystem where value flows both directions, where the intelligence I consume is matched by fair treatment of the intelligence I create.&lt;/p&gt;

&lt;p&gt;For now, that means blocking bad actors, allowing good ones, and watching carefully to see which category various AI providers end up in. It means supporting efforts like Cloudflare's Pay per Crawl even while doubting they'll achieve scale. It means engaging with the &lt;code&gt;llms.txt&lt;/code&gt; conversation while remaining skeptical of optimization that primarily benefits extractors.&lt;/p&gt;

&lt;p&gt;The old web had its problems. The new web is still being written. I'd like some say in what we write.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article reflects one creator's perspective on the rapidly evolving relationship between AI systems and content creators. The Tailwind CSS situation broke on January 6, 2026, with details from Adam Wathan's statements on GitHub and his &lt;a href="https://adams-morning-walk.transistor.fm/episodes/we-had-six-months-left" rel="noopener noreferrer"&gt;"We had six months left" episode&lt;/a&gt; of the Adam's Morning Walk podcast.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS situation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/tailwindlabs/tailwindcss.com/pull/2388" rel="noopener noreferrer"&gt;GitHub PR #2388&lt;/a&gt; - The llms.txt pull request and Adam Wathan's response&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://adams-morning-walk.transistor.fm/episodes/we-had-six-months-left" rel="noopener noreferrer"&gt;"We had six months left"&lt;/a&gt; - Adam Wathan's podcast episode discussing the layoffs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare's AI efforts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.cloudflare.com/introducing-pay-per-crawl/" rel="noopener noreferrer"&gt;Introducing Pay per Crawl&lt;/a&gt; - Cloudflare's official announcement (July 2025)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.cloudflare.com/introducing-ai-crawl-control/" rel="noopener noreferrer"&gt;Introducing AI Crawl Control&lt;/a&gt; - General availability announcement with 402 customization&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techcrunch.com/2025/07/01/cloudflare-launches-a-marketplace-that-lets-websites-charge-ai-bots-for-scraping/" rel="noopener noreferrer"&gt;TechCrunch coverage&lt;/a&gt; - Independent analysis of Pay per Crawl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI integration approaches:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;llms.txt specification&lt;/a&gt; - The proposed standard for AI-friendly documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev/docs/llms" rel="noopener noreferrer"&gt;Svelte LLM documentation&lt;/a&gt; - Example of comprehensive llms.txt implementation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev/docs/mcp/overview" rel="noopener noreferrer"&gt;Svelte MCP Server&lt;/a&gt; - MCP server helping AI assistants write better Svelte code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.cogley.jp%2Fuploads%2F2026-01%2F01KEG47H1W1A8XFDFA6MAPQR4C.png" alt="JRC CleanShot Microsoft Edge 2026-01-09-095706JST@2x.png" width="4384" height="2642"&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/ai-vs-creator-dilemma" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://esolia.co.jp/en/about/team/" rel="noopener noreferrer"&gt;Rick Cogley&lt;/a&gt; is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>business</category>
    </item>
    <item>
      <title>A Bowl of Renewal: Nanakusa-gayu</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:12:03 +0000</pubDate>
      <link>https://dev.to/rickcogley/a-bowl-of-renewal-nanakusa-gayu-3jod</link>
      <guid>https://dev.to/rickcogley/a-bowl-of-renewal-nanakusa-gayu-3jod</guid>
      <description>&lt;h2&gt;
  
  
  A Bowl of Renewal: Nanakusa-gayu on January 7th
&lt;/h2&gt;

&lt;p&gt;Today marks &lt;em&gt;Jinjitsu no Sekku&lt;/em&gt; (人日の節句)—the Festival of Humanity, one of the five sacred seasonal festivals in Japan. And with it comes one of the gentlest traditions in the Japanese calendar: &lt;em&gt;nanakusa-gayu&lt;/em&gt;, the seven herbs rice porridge. &lt;/p&gt;

&lt;p&gt;There's something almost paradoxical about this dish. After the richness of osechi-ryori, the toasts of sake, and the festive indulgence of the New Year period, we return to something deliberately plain. A simple rice porridge with seven wild herbs. Steam rising from a bowl in the morning. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Seven Herbs: A Poem You Can Eat
&lt;/h3&gt;

&lt;p&gt;The seven spring herbs (&lt;em&gt;haru no nanakusa&lt;/em&gt;) have been memorized for generations through a 5-7-5-7-7 waka rhythm—the same meter as classical Japanese poetry:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Seri, nazuna&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Gogyō, hakobera&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hotoke no za&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suzuna, suzushiro&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kore zo nanakusa&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In English, these translate roughly to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seri&lt;/strong&gt; (芹) — Japanese parsley / water dropwort&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nazuna&lt;/strong&gt; (薺) — shepherd's purse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gogyō&lt;/strong&gt; (御形) — Jersey cudweed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hakobera&lt;/strong&gt; (繁縷) — chickweed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hotoke no za&lt;/strong&gt; (仏の座) — henbit / nipplewort&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suzuna&lt;/strong&gt; (菘) — turnip&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suzushiro&lt;/strong&gt; (蘿蔔) — daikon radish&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fact that this knowledge persists as poetry rather than a mere list tells you something Japanese culture, it's much easier to memorize something when it's a poem or song. My daughter asked if I knew this saying, as she rattled it off. I replied "you grew up here" but it was kind of a weak response, so I'm writing this as a kind of penance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Little Cultural Aspects Make a Life
&lt;/h3&gt;

&lt;p&gt;Some might dismiss nanakusa-gayu as a quaint relic, an optional tradition in an age of convenience store breakfasts and global cuisine. And technically, yes—skipping it won't end your world.&lt;/p&gt;

&lt;p&gt;But here's my view: culture &lt;em&gt;is&lt;/em&gt; the world we live in. It's the accumulated texture of human experience, the small rituals that transform mere existence into something richer. The difference between a life with nanakusa-gayu and one without isn't life or death, but to me, it's far more interesting to have small traditions like this. &lt;/p&gt;

&lt;p&gt;When you sit down to a bowl of herb porridge on January 7th, you're participating in something that stretches back over a thousand years to Heian-era aristocrats, who themselves adapted it from Chinese traditions even older. You're not just having breakfast—you're briefly touching the same moment that countless generations have touched before you. I think it's pretty cool. &lt;/p&gt;

&lt;p&gt;Speaking of culture, the current season in the &lt;a href="https://cogley.jp/articles/72-microseasons" rel="noopener noreferrer"&gt;72 microseasons of Japan&lt;/a&gt; also reference parsley: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;芹乃栄 Seri sunawachi sakau, Parsley flourishes &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Practical Wisdom
&lt;/h3&gt;

&lt;p&gt;The traditional explanation holds that nanakusa-gayu "wards off evil spirits and prevents all illness" (&lt;em&gt;jaki wo harai manbyō wo nozoku&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The modern interpretation? Your stomach probably needs a break after a week of festive eating, and these early spring greens provide vitamins and minerals that heavy New Year dishes lack. Both explanations point to the same wisdom: reset, restore, begin again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make It Your Own
&lt;/h3&gt;

&lt;p&gt;Here's something freeing: you don't need to source all seven traditional herbs. The spirit of the dish matters more than botanical exactness. Use what's in your refrigerator—spinach, mitsuba, komatsuna, or whatever greens are fresh and available. Create your own &lt;em&gt;original nanakusa-gayu&lt;/em&gt;. But also note, you &lt;strong&gt;can&lt;/strong&gt; buy a "nanakusa set" of these greens at typical supermarkets, to chop up yourself. &lt;/p&gt;

&lt;p&gt;And if you use leftover rice from your freezer? You're honoring another Japanese value: &lt;em&gt;mottainai&lt;/em&gt;, the reduction of waste. The old traditions and modern sustainability turn out to walk the same path.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Make Nanakusa-gayu
&lt;/h3&gt;

&lt;p&gt;The preparation is almost meditative in its simplicity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cook rice into a loose porridge (&lt;em&gt;okayu&lt;/em&gt;), about 1 part rice to 5-7 parts water&lt;/li&gt;
&lt;li&gt;Finely chop your seven herbs (or your chosen greens)&lt;/li&gt;
&lt;li&gt;Add them to the porridge near the end of cooking&lt;/li&gt;
&lt;li&gt;Season simply with salt&lt;/li&gt;
&lt;li&gt;Eat in the morning, ideally while the house is still quiet&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No complex technique. No impressive plating. Just warmth, greenness, and the first taste of spring in the coldest part of winter.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Happy Jinjitsu no Sekku. May your year be gentle on the stomach and rich in meaning.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aij4mdul7s4n4rxtnwz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aij4mdul7s4n4rxtnwz.jpg" alt="Fresh leafy greens in a grocery store display, surrounded by colorful bottles and products." width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foit8dtrrxs1vosk8k5ya.jpg" alt="A bowl of rice porridge with green vegetables sits on a wooden table. The dish appears to be a healthy and nutritious meal." width="800" height="659"&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/a-bowl-of-renewal-nanakusa-gayu" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rick Cogley is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>japan</category>
    </item>
    <item>
      <title>What Is Kanreki? Japan's 60th Birthday Tradition Explained</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:11:48 +0000</pubDate>
      <link>https://dev.to/rickcogley/what-is-kanreki-japans-60th-birthday-tradition-explained-4hin</link>
      <guid>https://dev.to/rickcogley/what-is-kanreki-japans-60th-birthday-tradition-explained-4hin</guid>
      <description>&lt;p&gt;The morning of January 1st, 2026, I turned 60 — a milestone that in Japan carries a significance far beyond the usual birthday fanfare. I celebrated my &lt;strong&gt;kanreki&lt;/strong&gt; (還暦), one of the most meaningful longevity celebrations in Japanese culture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kanreki?
&lt;/h2&gt;

&lt;p&gt;The word itself tells the story: &lt;em&gt;kan&lt;/em&gt; (還) means "return" and &lt;em&gt;reki&lt;/em&gt; (暦) means "calendar." At 60, you've completed a full cycle of the traditional East Asian zodiac calendar and symbolically returned to your birth year.&lt;/p&gt;

&lt;p&gt;Think of it like an odometer rolling over. The zodiac system combines 12 animals (the &lt;em&gt;jūnishi&lt;/em&gt;) with 5 elements (wood, fire, earth, metal, water), each appearing in both yin and yang forms. Multiply these together — &lt;code&gt;12 × 5 = 60&lt;/code&gt; — and you get the complete cycle. When you turn 60, the same animal-element combination that marked your birth year comes around again.&lt;/p&gt;

&lt;p&gt;I was born in 1966, the Year of the Fire Horse — &lt;strong&gt;hinoe-uma&lt;/strong&gt; (丙午) in Japanese. This year, 2026, is once again hinoe-uma. The circle is complete. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Fire Horse's Shadow
&lt;/h2&gt;

&lt;p&gt;Being born hinoe-uma carries a peculiar weight in Japan. An old superstition holds that women born in Fire Horse years possess a fierce, strong-willed temperament that brings misfortune to their husbands—even, in darker versions of the tale, driving them to early graves. The belief is baseless, of course, but its demographic impact was strikingly real: Japan's birth rate dropped by roughly 25% in 1966 as families avoided having daughters in that year.&lt;/p&gt;

&lt;p&gt;The superstition traces back centuries, reinforced by kabuki plays and folk tales featuring hinoe-uma women as dangerous figures. It's a sobering example of how cultural narratives can shape actual human decisions at a national scale.&lt;/p&gt;

&lt;p&gt;For those of us born male in 1966, the stigma never applied directly—though we still carry that fire-horse energy, for whatever that's worth. And now, with 2026 marking the first hinoe-uma year since 1966, demographers are watching closely to see whether the old belief still influences family planning in modern Japan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Red of Rebirth
&lt;/h2&gt;

&lt;p&gt;The most recognizable symbol of kanreki is the &lt;strong&gt;akai chanchanko&lt;/strong&gt; — a red padded vest traditionally worn by the celebrant, often paired with a red cap called an &lt;em&gt;eboshi&lt;/em&gt; or &lt;em&gt;zukin&lt;/em&gt;. Red carries deep meaning here: it's the color associated with babies in Japan, representing a symbolic rebirth. Having completed one full life cycle, you begin another.&lt;/p&gt;

&lt;p&gt;There's also a practical folk belief at work. Red was thought to ward off evil spirits and illness—protection that seemed especially appropriate as one entered the later chapters of life.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tradition Transformed
&lt;/h2&gt;

&lt;p&gt;Kanreki's meaning has shifted over generations. When life expectancy was shorter, reaching 60 genuinely meant entering old age. The celebration acknowledged that you'd lived a full life and were now an elder deserving of respect and, frankly, rest.&lt;/p&gt;

&lt;p&gt;Today, with people commonly living into their 80s and 90s, 60 feels less like an ending and more like a transition—perhaps the start of a new chapter rather than the final pages. Many people are still working, traveling, and starting new projects at 60. The "rebirth" metaphor feels apt in a different way: not returning to infancy, but having the freedom and experience to begin something new.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Longevity Calendar
&lt;/h2&gt;

&lt;p&gt;Kanreki is just the first in a series of Japanese age celebrations, each with its own name and meaning:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Age&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;還暦 (kanreki)&lt;/td&gt;
&lt;td&gt;Return of the calendar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;古希 (koki)&lt;/td&gt;
&lt;td&gt;Rare since ancient times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;喜寿 (kiju)&lt;/td&gt;
&lt;td&gt;Joy and celebration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;傘寿 (sanju)&lt;/td&gt;
&lt;td&gt;Umbrella year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;米寿 (beiju)&lt;/td&gt;
&lt;td&gt;Rice year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;卒寿 (sotsuju)&lt;/td&gt;
&lt;td&gt;Graduation year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;白寿 (hakuju)&lt;/td&gt;
&lt;td&gt;White longevity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;百寿 (hyakuju)&lt;/td&gt;
&lt;td&gt;Century celebration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each milestone builds on the last, a kind of curriculum for growing old with intention and community recognition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beginning Again
&lt;/h2&gt;

&lt;p&gt;Standing at this threshold, I find myself thinking less about what I've accumulated and more about what comes next. The zodiac has reset. The calendar has returned to its starting point. The Fire Horse rides again.&lt;/p&gt;

&lt;p&gt;There's something clarifying about that image—the idea that experience doesn't just pile up linearly but can circle back, offering a fresh vantage point on familiar terrain. Sixty years of context, zero years into the next cycle.&lt;/p&gt;

&lt;p&gt;The chanchanko may be a bit theatrical for daily wear, but the idea it represents stays with me: that reaching a milestone isn't about looking backward at the distance covered, but about recognizing you're standing at a new beginning.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How do you mark significant birthdays? I'd be curious to hear about milestone traditions from other cultures.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g6qalc4zkt8ei1sh1o9.jpeg" alt="Man in a celebratory red kanreki (60th birthday) hat and " width="800" height="1066"&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/kanreki-completing-the-circle-at-60" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rick Cogley is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>japan</category>
    </item>
    <item>
      <title>The 72 Microseasons of Japan</title>
      <dc:creator>Rick Cogley</dc:creator>
      <pubDate>Sun, 22 Mar 2026 02:11:29 +0000</pubDate>
      <link>https://dev.to/rickcogley/the-72-microseasons-of-japan-39fk</link>
      <guid>https://dev.to/rickcogley/the-72-microseasons-of-japan-39fk</guid>
      <description>&lt;h2&gt;
  
  
  Finding Poetry in Nature's Subtle Changes
&lt;/h2&gt;

&lt;p&gt;In Japan, the changing seasons have always held deep cultural significance. While most of us experience a typical four seasons, traditional Japanese culture recognizes a more nuanced progression of time: the &lt;strong&gt;72 microseasons&lt;/strong&gt; (七十二候, &lt;em&gt;shichijūni kō&lt;/em&gt;). I think it's this focus that leads to Japanese people asking visitors if they have seasons in their countries. &lt;/p&gt;

&lt;h3&gt;
  
  
  A Calendar of Poetic Moments
&lt;/h3&gt;

&lt;p&gt;The 72 microseasons divide the year into periods of roughly five days each. Unlike the Western calendar's rigid months, these divisions mark subtle natural phenomena: &lt;em&gt;Harukaze kōri o toku&lt;/em&gt; ("East wind melts the ice"), &lt;em&gt;Sakura hajimete saku&lt;/em&gt; ("First cherry blossoms"), &lt;em&gt;Higurashi naku&lt;/em&gt; ("Evening cicadas sing").&lt;/p&gt;

&lt;p&gt;This system originated in ancient China and was adapted for Japan's climate around the 8th century AD. It's built on top of the 24 solar terms ("&lt;em&gt;sekki&lt;/em&gt;"), which themselves divide the year into ~15-day periods aligned with the sun's position.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Year → 4 Seasons → 24 Solar Terms → 72 Microseasons
       (3 months)    (~15 days)       (~5 days)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each solar term contains three microseasons:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solar Term&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Example Microseasons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;立春 Risshun&lt;/td&gt;
&lt;td&gt;Start of Spring&lt;/td&gt;
&lt;td&gt;East wind melts ice, Warblers sing, Fish emerge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;夏至 Geshi&lt;/td&gt;
&lt;td&gt;Summer Solstice&lt;/td&gt;
&lt;td&gt;Self-heal withers, Irises bloom, Crow-dipper sprouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;秋分 Shūbun&lt;/td&gt;
&lt;td&gt;Autumn Equinox&lt;/td&gt;
&lt;td&gt;Thunder ceases, Insects hole up, Farmers drain fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;冬至 Tōji&lt;/td&gt;
&lt;td&gt;Winter Solstice&lt;/td&gt;
&lt;td&gt;Self-heal sprouts, Deer shed antlers, Wheat sprouts under snow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why This Matters Today
&lt;/h3&gt;

&lt;p&gt;In our age of climate control and supermarket seasons, we've largely disconnected from nature's rhythms. The microseasons offer a gentle invitation to pay attention again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notice the small things&lt;/strong&gt;: The first hint of plum blossoms, the return of swallows, the way morning dew appears differently through the year.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embrace impermanence&lt;/strong&gt;: Each microseason lasts only five days. Like everything beautiful, it passes. This echoes the Buddhist concept of &lt;em&gt;mujō&lt;/em&gt; (無常).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live seasonally&lt;/strong&gt;: Traditional Japanese cuisine (&lt;em&gt;washoku&lt;/em&gt;) emphasizes seasonal ingredients. The microseasons provide a framework for eating with nature's rhythm.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Microseasons in Practice
&lt;/h3&gt;

&lt;p&gt;On this site, you'll see a microseason card in the sidebar showing the current period along with its poetic Japanese name. It's a small reminder that time moves not just in hours and days, but in the blooming of peonies and the flight of geese.&lt;/p&gt;

&lt;p&gt;Some favorites throughout the year:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;櫻始開 (&lt;em&gt;Sakura hajimete saku&lt;/em&gt;) — First cherry blossoms (Mar 26-30)&lt;/li&gt;
&lt;li&gt;虹始見 (&lt;em&gt;Niji hajimete arawaru&lt;/em&gt;) — First rainbows (Apr 15-19)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Summer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;腐草為螢 (&lt;em&gt;Kusaretaru kusa hotaru to naru&lt;/em&gt;) — Rotting grass becomes fireflies (Jun 11-15)&lt;/li&gt;
&lt;li&gt;蓮始開 (&lt;em&gt;Hasu hajimete hiraku&lt;/em&gt;) — First lotus blossoms (Jul 12-16)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Autumn&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;菊花開 (&lt;em&gt;Kiku no hana hiraku&lt;/em&gt;) — Chrysanthemums bloom (Oct 13-17)&lt;/li&gt;
&lt;li&gt;楓蔦黄 (&lt;em&gt;Momiji tsuta kibamu&lt;/em&gt;) — Maple leaves turn yellow (Nov 2-6)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Winter&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;熊蟄穴 (&lt;em&gt;Kuma ana ni komoru&lt;/em&gt;) — Bears retreat to their dens (Dec 12-16)&lt;/li&gt;
&lt;li&gt;雪下出麦 (&lt;em&gt;Yuki watarite mugi nobiru&lt;/em&gt;) — Wheat sprouts under snow (Jan 1-4)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nippon.com/en/features/h00124/" rel="noopener noreferrer"&gt;Nippon.com: Japan's 72 Microseasons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kyotojournal.org/uncategorized/the-72-japanese-micro-seasons/" rel="noopener noreferrer"&gt;Kyoto Journal: The 72 Micro Seasons&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kurashikaru.com/72-seasons/" rel="noopener noreferrer"&gt;Kurashi no Gendai: 72 Seasons App&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;The microseason card on this site updates daily, showing you where we are in this ancient cycle. Take a moment to notice what's happening in nature around you—you might be surprised how closely it matches these centuries-old observations.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd9rjm68a8gjyazt6lra4.jpeg" alt="A ripe persimmons fruit on a tree branch, surrounded by natural green foliage, under a sunny sky." width="800" height="533"&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://cogley.jp/articles/72-microseasons" rel="noopener noreferrer"&gt;cogley.jp&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rick Cogley is CEO of &lt;a href="https://esolia.co.jp/en/" rel="noopener noreferrer"&gt;eSolia Inc.&lt;/a&gt;, providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>japan</category>
    </item>
  </channel>
</rss>
