<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Foldkit Blog</title>
<link>https://foldkit.dev/blog</link>
<atom:link href="https://foldkit.dev/blog/rss.xml" rel="self" type="application/rss+xml" />
<description>Announcements, musings, and technical deep dives.</description>
<lastBuildDate>Fri, 04 Sep 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Foldkit 0.158.0</title>
  <link>https://foldkit.dev/blog/foldkit-0-158-0</link>
  <guid>https://foldkit.dev/blog/foldkit-0-158-0</guid>
  <description>Machine gains shared transitions, read-only context, simpler update integration, and a guide to defining, testing, and analyzing your application's workflows.</description>
  <pubDate>Fri, 04 Sep 2026 00:00:00 GMT</pubDate>
  <enclosure url="https://foldkit.dev/blog/foldkit-0-158-0/cover.webp" length="54872" type="image/webp" />
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><div class="relative overflow-hidden rounded-lg mb-8" data-foldkit-identity="80a2c6e532125e5c"><div class="absolute inset-0 bg-gray-200 dark:bg-gray-800 animate-pulse"></div><img width="3600" height="2400" class="relative w-full h-auto" src="https://foldkit.dev/blog/foldkit-0-158-0/cover.webp" alt="Oversized rows of the word machine cropped against a black background, with one row in lime green and the others in light gray."></div><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="foldkit-0-158-0" data-foldkit-identity="89d40481a105ec00">Foldkit 0.158.0</h1><p class="text-gray-500 dark:text-gray-400">September 4, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit 0.158.0 is out. This release focuses on Machine, Foldkit's experimental API for modeling workflows as typed transition tables.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Imagine a checkout where <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ClickedContinue</code> moves Cart to Shipping, Shipping to Payment, and Payment to Review. A Machine collects those rules in one table so you can read the allowed transitions together, test them, and generate a diagram from the same definition.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Machine has been available for a while. This release makes it easier to use inside an application, and the new <a class="link-accent" href="https://foldkit.dev/core/machine" data-foldkit-identity="b9c1739f4bc7044a">Machine guide</a> covers when to reach for it, how to integrate it, and how to keep a larger flow readable. It still ships from <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/experimental</code> while the API settles.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="edges-return-update-records">Edges return update records</h2><a aria-label="Copy link to Edges return update records" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#edges-return-update-records" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">A Machine Edge now returns the same shape as update: <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">{ model }</code>, with <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">commands</code> when it has work to run.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Previously, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">to</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">when</code> took a state builder and a separate Commands callback. In this example, both callbacks build the same order:</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Before:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">to</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#3D6B06;--shiki-dark:#BCDF59">  'Placing'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> state</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> CheckoutState</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Placing</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> order</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> orderFromReview</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">state</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> state</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">PlaceOrder</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> order</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> orderFromReview</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">state</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy Machine Edge before 0.158.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">After:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">to</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'Placing'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> state</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">  const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> nextOrder</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> orderFromReview</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">state</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">  return</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> CheckoutState</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Placing</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> order</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> nextOrder</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    commands</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">PlaceOrder</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> order</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> nextOrder</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  }</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy Machine Edge in 0.158.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The handler derives the order once and uses it for both the next state and the Command. The Command still executes through the Runtime after update returns.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This is a breaking change. Wrap existing state returns in <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">{ model }</code>, and move any separate Commands callback into the same handler's <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">commands</code> field.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thank you to <a class="link-accent" href="https://github.com/hdoro" data-foldkit-identity="b9c1739f4bc7044a">@hdoro</a> for suggesting that Machine Edges return Model and Commands together!</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="shared-transitions">Shared transitions</h2><a aria-label="Copy link to Shared transitions" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#shared-transitions" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">A checkout may allow cancellation from Cart, Shipping, Payment, and Review. Repeating that Edge under every state makes it harder to see which transitions actually differ.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.forStates(...).on(...)</code> now lets you declare the cancellation once in the Machine's <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">shared</code> array:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">shared</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Machine</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">forStates</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">([</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'Cart'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'Shipping'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'Payment'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'Review'</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">])</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">on</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    ClickedCancel</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> to</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'Cancelled'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> state</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">      model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> CheckoutState</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Cancelled</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">        isShippingRequired</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> state</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">isShippingRequired</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">      }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">    }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">))</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">]</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy shared checkout cancellation to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The handler's <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">state</code> narrows to the selected variants, so it can read their common fields. A state-local transition overrides the shared default for the same Message. Overlapping shared declarations throw when the Machine is defined.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Shared transitions also appear in the Machine's graph analysis and Mermaid output.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="folding-a-machine-into-update">Folding a Machine into update</h2><a aria-label="Copy link to Folding a Machine into update" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#folding-a-machine-into-update" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.fold</code> handles the work of reading a Machine state from the enclosing Model, running a transition, writing the next state back, and returning its Commands:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldCheckout</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Machine</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">fold</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  machine</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> checkoutMachine</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">  read</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">)</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> Option</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">some</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">checkout)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">  write</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> nextCheckout</span><span style="color:#625E75;--shiki-dark:#9E9BAA">)</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> evo</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> checkout</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> nextCheckout</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy folding checkout into the application Model to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Call <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldCheckout(model, message)</code> from update, or use <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldCheckout(message)</code> as a Step in <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Update.combine</code>. If the Machine exists only in some Model variants, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">read</code> can return <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Option.none()</code> elsewhere and the fold leaves the Model unchanged.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Machines can also declare a Schema for read-only context. For example, a checkout guard can consult current inventory held elsewhere in the Model without copying it into every checkout state. <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.fold</code> reads that context from the enclosing Model for each transition. Direct calls to <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">transition</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">step</code> must supply it too.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="ignored-messages">Ignored Messages</h2><a aria-label="Copy link to Ignored Messages" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#ignored-messages" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.step</code> now explains why a Message produced no transition:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">OutOfAlphabet</code>: the Message tag appears nowhere in the table.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">NotApplicable</code>: the table knows the Message, but the current state has no Edge for it.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">GuardsFellThrough</code>: every guard declined and there was no fallback.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ExplicitlyIgnored</code>: evaluation reached the new <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ignore()</code> fallback.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Use <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ignore()</code> at the end of a guard list when doing nothing is intentional. Tests and inspectors can distinguish that decision from guards that fell through without a fallback. Static analysis also reports Edges after <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ignore()</code> as dead transitions.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">If you construct or assert an <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Ignored</code> result, add its required <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">reason</code> field. Exhaustive matches over <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">GuardedEdge</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">IgnoredReason</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">DeadTransitionReason</code> also need to handle the new ignore cases.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="more-in-this-release">More in this release</h2><a aria-label="Copy link to More in this release" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#more-in-this-release" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.define</code> accepts nested state unions without throwing at module load.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Reachability analysis now excludes Edges shadowed by an earlier <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">otherwise</code>, and dead-transition reports list each Edge once.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.StateTransitions</code> preserves state and Message narrowing when you extract a state's transition entry into a named binding.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The docs have larger text, more space between sections, Inter for body text, and Paper Mono for code. Search is also available from the landing page.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The <a class="link-accent" href="https://foldkit.dev/core/machine" data-foldkit-identity="b9c1739f4bc7044a">Machine guide</a> walks through guards, context, shared transitions, update integration, graph analysis, and pure tests. The <a class="link-accent" href="https://foldkit.dev/example-apps/state-machine" data-foldkit-identity="b9c1739f4bc7044a">state machine example</a> lets you step through a checkout and inspect its transitions as you go.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The full <a class="link-accent" href="https://github.com/foldkit/foldkit/releases/tag/foldkit%400.158.0" data-foldkit-identity="b9c1739f4bc7044a">0.158.0 release notes</a> cover every package and migration detail.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thanks to everyone building with Foldkit!</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Devin</p></div></article>]]></content:encoded>
</item>
<item>
  <title>Foldkit 0.156.0 and 0.157.0</title>
  <link>https://foldkit.dev/blog/foldkit-0-157-0</link>
  <guid>https://foldkit.dev/blog/foldkit-0-157-0</guid>
  <description>Two new recommended @foldkit/oxlint-plugin rules, less boilerplate when matching refined unions, plus updates to Story, Scene, Mount, @foldkit/ui, and Foldkit's Vite plugin.</description>
  <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
  <enclosure url="https://foldkit.dev/blog/foldkit-0-157-0/cover.webp" length="19518" type="image/webp" />
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><div class="relative overflow-hidden rounded-lg mb-8" data-foldkit-identity="80a2c6e532125e5c"><div class="absolute inset-0 bg-gray-200 dark:bg-gray-800 animate-pulse"></div><img width="1600" height="1067" class="relative w-full h-auto" src="https://foldkit.dev/blog/foldkit-0-157-0/cover.webp" alt="The number 157 in large cyan type layered over the number 156 in coral on a light gray background."></div><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="foldkit-0-157-0" data-foldkit-identity="89d40481a105ec00">Foldkit 0.156.0 and 0.157.0</h1><p class="text-gray-500 dark:text-gray-400">September 2, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit 0.157.0 and 0.156.0 are out.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">0.156.0 was a small release.</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">HoverIntent now closes without the pointer grace delay when focus leaves the trigger and panel.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Parents can programmatically close HoverIntent with <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">HoverIntent.close</code>, for example after a user selects a menu item.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Now for 0.157.0. The appetizer is improved type safety in Story and Scene tests. The main dish is <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/oxlint-plugin</code> updates, with a side of more ergonomic OutMessage matching.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="better-foldkit-linting">Better Foldkit linting</h2><a aria-label="Copy link to Better Foldkit linting" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#better-foldkit-linting" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The recommended <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/oxlint-plugin</code> preset gained two rules.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/no-impure-call-at-decision-time</code> reports time and randomness calls made while update is deciding which Commands to return, instead of when those Commands execute.</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Save</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Command</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">define</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'Save'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  args</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> createdAt</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Schema</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">Number </span><span style="color:#625E75;--shiki-dark:#9E9BAA">},</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  messages</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#885A06;--shiki-dark:#FFCA58">Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">CompletedSave]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">  execute</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> createdAt</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">succeed</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">CompletedSave</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#885A06;--shiki-dark:#FFCA58"> createdAt</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">))</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> save</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">):</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Return</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">></span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">  // Wrong: Date.now() runs while update is deciding which Commands to return.</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  commands</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Save</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> createdAt</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Date</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">now</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">() </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy reading the clock while building a Command to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Move the read into the Command's Effect and return the value in its result Message:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Save</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Command</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">define</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'Save'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  messages</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#885A06;--shiki-dark:#FFCA58">Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">CompletedSave]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">  // Right: the clock is read only when the Command executes.</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  execute</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Clock</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">currentTimeMillis</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">pipe</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">map</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">createdAt</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">CompletedSave</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#885A06;--shiki-dark:#FFCA58"> createdAt</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">))</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  )</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> save</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">):</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Return</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">></span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  commands</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Save</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">()]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy reading the clock when the Command executes to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/prefer-effect-module-names</code> reports abbreviated and trailing-underscore Effect import aliases, and auto-fixes aliases when it can identify the exported module name.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Existing applications using the preset may produce new lint errors after upgrading.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Across the preset, rules now recognize aliases for Foldkit, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/ui</code>, Effect, and child Message imports. They ignore local shadows and no longer mistake unrelated <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">message</code> fields for Submodel Messages.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The preset already reported parent code that constructs a child Message. In 0.157.0, the rule also catches that code when the child import is aliased. For example, a parent should not decide that opening a Dialog means dispatching its <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">RequestedOpen</code> Message:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldDialogOpen</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">foldChildStep</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">  // Wrong: the parent constructs one of Dialog's internal Messages.</span></span>
<span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">  update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">)</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    Dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">update</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">RequestedOpen</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">())</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  read</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> readDialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  write</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> writeDialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  toParentMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> toGotDialogMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  foldOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldDialogOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy a parent constructing a Dialog Message to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Dialog exposes <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">open</code>, so the parent can fold that operation without reaching into the child's Messages. Parents should drive child behavior through update functions exposed by the child:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldDialogOpen</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">foldChildStep</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">  // Right: the parent calls an update function owned by Dialog.</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Dialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">open</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  read</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> readDialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  write</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> writeDialog</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  toParentMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> toGotDialogMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  foldOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldDialogOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy folding a child-owned update function to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This lets the parent call a child-owned update function without depending on the child's internal Messages. <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Update.foldChildStep</code> still handles the child Model, Commands, and OutMessages. Animation in <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/ui</code> now exposes <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">show</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">hide</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">toggle</code> update functions for the same reason.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="refined-union-matching">Refined union matching</h2><a aria-label="Copy link to Refined union matching" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#refined-union-matching" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit union matchers now accept a structurally refined union as their optional second type argument. For a Listbox whose value is refined to <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Plan</code>, this lets <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Listbox.OutMessage.match</code> keep <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">value</code> typed as <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Plan</code> without a separate Effect Match pipeline.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Before:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">// Before: preserving Plan requires a separate Effect Match pipeline.</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldListboxOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Match</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">type</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Listbox</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">OutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Plan</span><span style="color:#625E75;--shiki-dark:#9E9BAA">>></span><span style="color:#403D4A;--shiki-dark:#E0DEE6">()</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">pipe</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Match</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">withReturnType</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Step</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">>></span><span style="color:#403D4A;--shiki-dark:#E0DEE6">()</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Match</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">tagsExhaustive</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    Selected</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">      ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> value</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">      model</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> evo</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> maybePlan</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> Option</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">some</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">value</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy refined OutMessage fold before 0.157.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">After:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">// After: Foldkit's matcher preserves Plan directly.</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> foldListboxOutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Listbox</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">OutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">match</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Update</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">Step</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#885A06;--shiki-dark:#FFCA58">Model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Message</span><span style="color:#625E75;--shiki-dark:#9E9BAA">>,</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Listbox</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">OutMessage</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#885A06;--shiki-dark:#FFCA58">Plan</span><span style="color:#625E75;--shiki-dark:#9E9BAA">></span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">></span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  Selected</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">    ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> value</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    model</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> evo</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">model</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> maybePlan</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#885A06;--shiki-dark:#FFCA58"> Option</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">some</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">value</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy refined OutMessage fold in 0.157.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The new form is shorter, remains exhaustive, and keeps <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">value</code> typed as <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Plan</code> in the <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Selected</code> handler.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Other changes:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Story and Scene now type-check Message and OutMessage steps against the update under test, with a new <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Story.steps</code> API for reusable Story sequences.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Mounts can use the new <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">viewStateChanges</code> Stream to stop DOM interaction while DevTools shows a historical view.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Textarea content must now use <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.Value</code>.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.unreachableStates</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Machine.deadTransitions</code> can now account for states entered through persistence or deep links.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Dialog now falls back to the first focusable element—or the dialog itself—when its requested focus target is missing or cannot receive focus.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">DevTools overlay dependencies are now preloaded, so Vite does not reload on first use.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The former Manifesto page is now <a class="link-accent" href="https://foldkit.dev/introduction/why-foldkit" data-foldkit-identity="b9c1739f4bc7044a">Why Foldkit</a>.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thank you to <a class="link-accent" href="https://github.com/rjdellecese" data-foldkit-identity="b9c1739f4bc7044a">@rjdellecese</a> for proposing the Effect module naming convention and Mount view-state API, and to <a class="link-accent" href="https://github.com/artile" data-foldkit-identity="b9c1739f4bc7044a">@artile</a> for reporting the Dialog focus issue. Thank you also to <a class="link-accent" href="https://github.com/armancharan" data-foldkit-identity="b9c1739f4bc7044a">@armancharan</a> for adding <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">vitest.config.ts</code> typechecking across the repo and <a class="link-accent" href="https://github.com/filipfalcon" data-foldkit-identity="b9c1739f4bc7044a">@filipfalcon</a> for the Vite preload fix!</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The full <a class="link-accent" href="https://github.com/foldkit/foldkit/releases/tag/foldkit%400.156.0" data-foldkit-identity="b9c1739f4bc7044a">0.156.0</a> and <a class="link-accent" href="https://github.com/foldkit/foldkit/releases/tag/foldkit%400.157.0" data-foldkit-identity="b9c1739f4bc7044a">0.157.0</a> release notes cover every package and migration detail.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thanks to everyone building with Foldkit!</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Devin</p></div></article>]]></content:encoded>
</item>
<item>
  <title>Foldkit 0.154.0 and 0.155.0</title>
  <link>https://foldkit.dev/blog/foldkit-0-155-0</link>
  <guid>https://foldkit.dev/blog/foldkit-0-155-0</guid>
  <description>Foldkit 0.154.0 and 0.155.0 expand click and focus controls, unify Vite builds for server-rendered apps, introduce HoverIntent, and more.</description>
  <pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate>
  <enclosure url="https://foldkit.dev/blog/foldkit-0-155-0/cover.webp" length="52494" type="image/webp" />
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><div class="relative overflow-hidden rounded-lg mb-8" data-foldkit-identity="80a2c6e532125e5c"><div class="absolute inset-0 bg-gray-200 dark:bg-gray-800 animate-pulse"></div><img width="1600" height="1067" class="relative w-full h-auto" src="https://foldkit.dev/blog/foldkit-0-155-0/cover.webp" alt="The version numbers 0.154.0 and 0.155.0 in large black type over layered translucent zeros on a lime-green background."></div><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="foldkit-0-155-0" data-foldkit-identity="89d40481a105ec00">Foldkit 0.154.0 and 0.155.0</h1><p class="text-gray-500 dark:text-gray-400">August 29, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit 0.155.0 is out. I never announced 0.154.0, so this post covers both.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="what-shipped-in-0-154-0">What shipped in 0.154.0</h2><a aria-label="Copy link to What shipped in 0.154.0" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#what-shipped-in-0-154-0" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">0.154.0 shipped earlier this week with several additions worth calling out:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnClick</code> gained <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">defaultAction</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">propagation</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">focusSelector</code> controls. Foldkit applies them synchronously before dispatching the Message, covering browser behavior such as preventing a default action, stopping DOM propagation, and focusing an existing element inside the originating gesture. <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnClickFocus</code> was deprecated in 0.154.0 and is removed in 0.155.0. Thank you to <a class="link-accent" href="https://github.com/hdoro" data-foldkit-identity="b9c1739f4bc7044a">@hdoro</a> for pointing out the limitation in <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnClick</code>!</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnFocusEnter</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnFocusLeave</code> model focus across a compound region. Put them on a common ancestor and Foldkit dispatches only when focus crosses that boundary, not when it moves between descendants.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Update.foldChild</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Update.foldChildStep</code> can now emit a derived parent OutMessage from <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldOutMessage</code>. A child fact can update the current Submodel and produce a different fact for its parent without making the parent Message handler inspect the child Message again.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Popover now supplies a headless <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">arrow</code> attribute bundle and keeps it positioned as the panel flips and shifts. You draw the arrow; Foldkit publishes the placement and offsets. Thank you to <a class="link-accent" href="https://github.com/foldkit/foldkit/pull/1125" data-foldkit-identity="b9c1739f4bc7044a">@wmaurer</a> for contributing it!</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/oxlint-plugin</code> gained <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/no-empty-to-parent-out-message</code>, which catches <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">toParentOutMessage</code> mappers that always return <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">undefined</code> and should be omitted.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Now for 0.155.0.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="one-vite-build-for-the-whole-application">One Vite build for the whole application</h2><a aria-label="Copy link to One Vite build for the whole application" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#one-vite-build-for-the-whole-application" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit gained <a class="link-accent" href="https://foldkit.dev/blog/foldkit-has-server-rendering" data-foldkit-identity="b9c1739f4bc7044a">server rendering</a> two weeks ago. It worked, but building a server-rendered application still happened across three commands: build the browser bundle, build the server bundle, then prerender the configured routes.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">That separation mattered as soon as another Vite plugin needed to participate in the build. A deployment adapter or host plugin could join the browser build and never see the server build that followed in a separate process. It could deploy half an application. The browser and server builds could also mint different build ids, producing HTML that hydration correctly refused.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/vite-plugin</code> 0.20.0 now owns the whole build:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">foldkit</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  buildId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  ssr</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    serverEntry</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> '/src/entry.server.ts'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">    build</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> prerender</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> true</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> },</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  },</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy Vite build configuration to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Run <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">vite build</code> once. Vite produces the browser bundle, the server bundle, and, when <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">prerender</code> is enabled, a page for every path exported by the server entry. Every environment belongs to the same build and shares the same build id.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The build also writes <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit.build.json</code> beside the server bundle. It records the output directories, server entry, and generated paths so a deployment host can derive its routing behavior from the artifact instead of asking you to repeat it in platform settings.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Applications without <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ssr.build</code> still produce a browser bundle exactly as before.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This work came from <a class="link-accent" href="https://github.com/foldkit/foldkit/pull/1174" data-foldkit-identity="b9c1739f4bc7044a">@filipfalcon</a>. Thank you!</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="mount-construction-is-pure-now">Mount construction is pure now</h2><a aria-label="Copy link to Mount construction is pure now" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#mount-construction-is-pure-now" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Mount.define</code> and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Mount.defineStream</code> used to take positional inputs, then accept their work in a second call. A Mount with args had a curried <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">args =&gt; element =&gt; Effect&lt;Message&gt;</code> body.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">It looked compact, but it hid a bad boundary: the outer function ran when view constructed the MountAction. Anything written between those arrows ran during a pure view, on every render, rather than when the element entered the DOM.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Mount definitions now use one config object with named <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">args</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">messages</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">execute</code> fields:</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Before:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> AnchorPopover</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Mount</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">define</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#3D6B06;--shiki-dark:#BCDF59">  'AnchorPopover'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  {</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> buttonId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Schema</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">String</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> anchor</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> AnchorConfig</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> },</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  CompletedAnchorPopover</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">)(</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">  ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> buttonId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> anchor</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    element</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">      Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">gen</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#B83530;--shiki-dark:#FF7272">function*</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">        // ...</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">      }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy Mount definition before 0.155.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">After:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> AnchorPopover</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Mount</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">define</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'AnchorPopover'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  args</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> buttonId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Schema</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">String</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> anchor</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#885A06;--shiki-dark:#FFCA58"> AnchorConfig</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> },</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  messages</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> [</span><span style="color:#885A06;--shiki-dark:#FFCA58">CompletedAnchorPopover</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">]</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">  execute</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ({</span><span style="color:#885A06;--shiki-dark:#FFCA58"> element</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> buttonId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> anchor</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> })</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">gen</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#B83530;--shiki-dark:#FF7272">function*</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#8A869C">      // ...</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">    }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy Mount definition in 0.155.0 to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">execute</code> has the same flat shape with or without args. Constructing a MountAction now runs nothing. The runtime calls <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">execute</code> only after the element exists.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This is a breaking change, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/ui</code> 0.155.0 now requires <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit</code> 0.155.0 because its Mount definitions use the new shape. The <a class="link-accent" href="https://foldkit.dev/core/mount" data-foldkit-identity="b9c1739f4bc7044a">Mount docs</a> have the complete migration.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thank you to <a class="link-accent" href="https://github.com/hdoro" data-foldkit-identity="b9c1739f4bc7044a">@hdoro</a> for suggesting this API change!</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="hoverintent-joins-foldkit-ui">HoverIntent joins @foldkit/ui</h2><a aria-label="Copy link to HoverIntent joins @foldkit/ui" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#hoverintent-joins-foldkit-ui" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">@foldkit/ui</code> now includes <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">HoverIntent</code>, a headless Submodel for interactions that reveal a panel from a trigger.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">It opens after a configurable pointer delay, stays open while the pointer or focus moves between the trigger and panel, and closes after a configurable grace period. Focus opens immediately. Escape closes immediately and prevents reopening until the pointer and focus have both left.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">HoverIntent returns trigger and panel event bundles. It does not create markup, choose ARIA roles, position the panel, or style anything. A Hover Card can pair it with Anchor. A hover menu can use it to keep its items available as the pointer moves from the trigger into the panel.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The <a class="link-accent" href="https://foldkit.dev/ui/hover-intent" data-foldkit-identity="b9c1739f4bc7044a">HoverIntent docs</a> have current examples and API details.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thank you to <a class="link-accent" href="https://github.com/foldkit/foldkit/pull/1179" data-foldkit-identity="b9c1739f4bc7044a">@SyahrulBhudiF</a> for contributing it!</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="smaller-apis-got-sharper-too">Smaller APIs got sharper too</h2><a aria-label="Copy link to Smaller APIs got sharper too" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#smaller-apis-got-sharper-too" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There is plenty more in 0.155.0:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">FieldValidation.match</code> exhaustively matches <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">NotValidated</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Validating</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Valid</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Invalid</code> field states without a manual Effect Match pipeline.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">CustomElement</code> builders now accept <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ChildAttribute</code> alongside ordinary attributes, so published Submodel attribute groups compose with them just like native HTML element builders. Thank you to <a class="link-accent" href="https://github.com/jay-zahiri" data-foldkit-identity="b9c1739f4bc7044a">@jay-zahiri</a> for noting this issue!</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Scene preserves every OutMessage emitted by one update-producing step in runtime order. Use <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">expectOutMessages</code> when a batch produces several.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The renderer does less work in its hot path by caching unchanged document metadata, writing ordinary properties directly, and skipping module scans when a VNode has no module data.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Dialog now releases its resources when <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">ShowDialog</code> fails or is interrupted, and when <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">CloseDialog</code> finds that the element is already gone. Thank you to <a class="link-accent" href="https://github.com/foldkit/foldkit/pull/1191" data-foldkit-identity="b9c1739f4bc7044a">@wmaurer</a> for the fixes!</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">ManagedResource now clears its reference and dispatches its release Message when the user-provided release effect fails.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The deprecated <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnClickFocus</code> is gone. Use <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">h.OnClick(message, { focusSelector })</code>.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The packages now build and test with TypeScript 7 as well. The full <a class="link-accent" href="https://github.com/foldkit/foldkit/releases/tag/foldkit%400.154.0" data-foldkit-identity="b9c1739f4bc7044a">0.154.0</a> and <a class="link-accent" href="https://github.com/foldkit/foldkit/releases/tag/foldkit%400.155.0" data-foldkit-identity="b9c1739f4bc7044a">0.155.0</a> release notes cover every package and migration detail.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Thanks to everyone building with Foldkit!</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Devin</p></div></article>]]></content:encoded>
</item>
<item>
  <title>Foldkit Has Server Rendering</title>
  <link>https://foldkit.dev/blog/foldkit-has-server-rendering</link>
  <guid>https://foldkit.dev/blog/foldkit-has-server-rendering</guid>
  <description>Your Foldkit application can now render to HTML at build time or per request, then hydrate on the client.</description>
  <pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
  <enclosure url="https://foldkit.dev/blog/foldkit-has-server-rendering/cover.webp" length="25924" type="image/webp" />
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><div class="relative overflow-hidden rounded-lg mb-8" data-foldkit-identity="80a2c6e532125e5c"><div class="absolute inset-0 bg-gray-200 dark:bg-gray-800 animate-pulse"></div><img width="1600" height="1066" class="relative w-full h-auto" src="https://foldkit.dev/blog/foldkit-has-server-rendering/cover.webp" alt="The letters SSR in cyan set over a large black circle on a coral background."></div><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="foldkit-has-server-rendering" data-foldkit-identity="89d40481a105ec00">Foldkit Has Server Rendering</h1><p class="text-gray-500 dark:text-gray-400">August 15, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Until today, Foldkit was purely an SPA framework. The server sent an HTML file to the client, the client requested the JavaScript bundle, and once it loaded, the application booted.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There are issues with this approach in isolation:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The user sees the server's HTML before the JavaScript loads. Unless you prerender HTML that matches the loaded app, that is typically a blank page.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There is no first-class way to do SSG. The workaround is a heavy prerender step: for example, using Playwright to visit each route and capture HTML (which the Foldkit website used to do).</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There is no first-class way to do request-time SSR: for example, the server sending the client an initial HTML file personalized to the logged-in user.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There was one more problem. Foldkit not having SSR was the deal-breaker for <a class="link-accent" href="https://x.com/MichaelArnaldi" data-foldkit-identity="b9c1739f4bc7044a">Michael Arnaldi</a>, BDFL of Effect. And that simply will not do.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><a class="link-accent" href="https://x.com/MichaelArnaldi/status/2059527426833592755" data-foldkit-identity="b9c1739f4bc7044a"><img class="max-w-full" src="https://foldkit.dev/blog/foldkit-has-server-rendering/michael-ssr-dealbreaker.webp" alt="Michael Arnaldi on X: &quot;Personally it's the deal breaker for me, I can't see myself writing 500+ lines of code to do SSG and not have a solution for SSR. I think we made the same mistakes over and over again, server side rendering is strictly necessary for good DX. I like the rest of the design.&quot;" data-foldkit-identity="0d4c8508e5bd39bb"></a></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">So, Foldkit can render on the server now. It shipped today under <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/experimental/server</code>. It will be promoted to <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/server</code> once the community puts it through its paces.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/experimental/server</code> ships <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">renderToString</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">foldkit/runtime</code> ships <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">hydrate</code>. Together, they give a Foldkit application two new ways to reach the browser: generate static HTML for selected routes during the build, or render each request on a server. When the server and client agree, the browser adopts the served HTML in place. A mismatched subtree is rebuilt, and an invalid or cross-deployment handoff is refused. The application that boots is the same Foldkit application you wrote yesterday.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">A Foldkit app can remain a pure client-side SPA. Server rendering is opt-in.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="ssg-vs-ssr">SSG vs SSR</h2><a aria-label="Copy link to SSG vs SSR" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#ssg-vs-ssr" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">SSG and SSR are the same thing run at different times. Either a build script calls your server entry once per URL and writes files, or a server calls it once per request and sends the response. The entry is a small module that derives flags from a <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Request</code> and asks Foldkit to render:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">import</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#B83530;--shiki-dark:#FF7272"> from</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'effect'</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">import</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Server</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#B83530;--shiki-dark:#FF7272"> from</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'foldkit/experimental'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">import</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#885A06;--shiki-dark:#FFCA58"> readCountCookie</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#B83530;--shiki-dark:#FF7272"> from</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> './cookie'</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">import</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Flags</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> init</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> view</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> }</span><span style="color:#B83530;--shiki-dark:#FF7272"> from</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> './main'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">const</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> flagsForRequest</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">request</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Request</span><span style="color:#625E75;--shiki-dark:#9E9BAA">):</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Flags</span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span><span style="color:#403D4A;--shiki-dark:#E0DEE6"> (</span><span style="color:#625E75;--shiki-dark:#9E9BAA">{</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  initialCount</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> readCountCookie</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">request</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">headers</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">get</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#3D6B06;--shiki-dark:#BCDF59">'cookie'</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">) </span><span style="color:#625E75;--shiki-dark:#9E9BAA">??</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> ''</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">}</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">export</span><span style="color:#B83530;--shiki-dark:#FF7272"> const</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> renderPage</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> (</span><span style="color:#885A06;--shiki-dark:#FFCA58">request</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Request</span><span style="color:#625E75;--shiki-dark:#9E9BAA">):</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> Promise</span><span style="color:#625E75;--shiki-dark:#9E9BAA">&#x3C;</span><span style="color:#0E7490;--shiki-dark:#49CAE4">Server</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#0E7490;--shiki-dark:#49CAE4">EntryResult</span><span style="color:#625E75;--shiki-dark:#9E9BAA">></span><span style="color:#B83530;--shiki-dark:#FF7272"> =></span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">  Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">runPromise</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#885A06;--shiki-dark:#FFCA58">    Effect</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">gen</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#B83530;--shiki-dark:#FF7272">function*</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> ()</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">      const</span><span style="color:#885A06;--shiki-dark:#FFCA58"> renderedApplication</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> =</span><span style="color:#B83530;--shiki-dark:#FF7272"> yield*</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Server</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">renderToString</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">        {</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Flags</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> init</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#885A06;--shiki-dark:#FFCA58"> view</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> },</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">        {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">          flags</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#6B5CB8;--shiki-dark:#A093E2"> flagsForRequest</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">request</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">          buildId</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#B83530;--shiki-dark:#FF7272"> import</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">meta</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">env</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#885A06;--shiki-dark:#FFCA58">FOLDKIT_BUILD_ID</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">        },</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">      )</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B83530;--shiki-dark:#FF7272">      return</span><span style="color:#885A06;--shiki-dark:#FFCA58"> Server</span><span style="color:#625E75;--shiki-dark:#9E9BAA">.</span><span style="color:#6B5CB8;--shiki-dark:#A093E2">Rendered</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">(</span><span style="color:#885A06;--shiki-dark:#FFCA58">renderedApplication</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">        headers</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#625E75;--shiki-dark:#9E9BAA"> {</span></span>
<span class="line"><span style="color:#3D6B06;--shiki-dark:#BCDF59">          'cache-control'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'private, no-store'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">          vary</span><span style="color:#625E75;--shiki-dark:#9E9BAA">:</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> 'cookie'</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">        },</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">      }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span></span>
<span class="line"><span style="color:#625E75;--shiki-dark:#9E9BAA">    }</span><span style="color:#403D4A;--shiki-dark:#E0DEE6">)</span><span style="color:#625E75;--shiki-dark:#9E9BAA">,</span></span>
<span class="line"><span style="color:#403D4A;--shiki-dark:#E0DEE6">  )</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy server entry example to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Flags</code> Schema, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">init</code>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">view</code>, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">flagsForRequest</code> are provided by you. The rest is wiring.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">The same entry serves Vite in development, a Node host in production, a build script for static generation, and fetch-native runtimes like Cloudflare Workers. Hosts are interchangeable because the entry deals only in Web standards: a Web <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Request</code> goes in, and the host turns the result into a Web <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">Response</code>.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="how-it-works">How it works</h2><a aria-label="Copy link to How it works" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#how-it-works" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit's view is a pure function of the Model, so most of server rendering was less "add SSR to the framework" and more "call the same function somewhere else."</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">If you are using Foldkit without SSR, the browser runs the whole sequence: your <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">flags</code> Effect produces the flags, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">init</code> turns them into the first Model, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">view</code> renders it.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">With SSR, the server runs the front of that sequence and the browser finishes it:</p><ul class="list-disc mb-6 space-y-2 [&amp;>li>p:last-child]:mb-0" data-foldkit-identity="408d5615f6a4615c"><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><strong data-foldkit-identity="53c122144cc5e287">On the server</strong>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">flagsForRequest</code> turns the request into flags, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">init</code> builds the first Model, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">view</code> renders it to HTML. That HTML ships to the browser with the flags serialized alongside it.</p></li><li data-foldkit-identity="54c53c1004e2635b"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><strong data-foldkit-identity="53c122144cc5e287">On the client</strong>, <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">init</code> runs again with those same flags, rebuilds the initial Model, and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">view</code> renders it. Hydration adopts matching server DOM and rebuilds a mismatched subtree from its nearest parent.</p></li></ul><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There is no separate server-side Model to reconcile with the client's. Both sides derive the initial Model from the same flags and <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">init</code>, then render it through the same <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">view</code>.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">After hydration, the initial Commands run, and your Foldkit application behaves like a typical SPA with client-side navigation.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="what-shipped">What shipped</h2><a aria-label="Copy link to What shipped" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#what-shipped" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Check out the new docs page on <a class="link-accent" href="https://foldkit.dev/core/server-rendering" data-foldkit-identity="b9c1739f4bc7044a">Server Rendering</a>, and the runnable <a class="link-accent" href="https://foldkit.dev/example-apps/ssg" data-foldkit-identity="b9c1739f4bc7044a">Static Site Generation</a> and <a class="link-accent" href="https://foldkit.dev/example-apps/ssr" data-foldkit-identity="b9c1739f4bc7044a">Server-Side Rendering</a> examples.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This website (<a class="link-accent" href="https://foldkit.dev" data-foldkit-identity="b9c1739f4bc7044a">foldkit.dev</a>) now prerenders every route through the same <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">renderPage</code> contract (SSG). The page you are reading hydrated in place.</p><div class="group flex items-center gap-1 md:hover-capable:gap-0 mt-16 mb-4 [h1+&amp;]:mt-12 md:hover-capable:flex-row-reverse md:hover-capable:justify-end md:hover-capable:-ml-[1.5rem]" data-foldkit-identity="caca73818d585138"><h2 class="font-heading font-book text-2xl md:text-3xl leading-snug tracking-tight text-gray-900 dark:text-white scroll-mt-6" id="what-this-means-for-foldkit">What this means for Foldkit</h2><a aria-label="Copy link to What this means for Foldkit" class="px-0.5 py-1 rounded transition-opacity text-gray-400 dark:text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 focus-visible:text-gray-800 dark:focus-visible:text-gray-200 focus-visible:opacity-100 cursor-pointer hover-capable:opacity-0 hover-capable:group-hover:opacity-100" href="#what-this-means-for-foldkit" data-foldkit-identity="0e98846c7a53f64f"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="a71898c5469a9bb4"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"></path></svg></a></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Foldkit is not getting a second programming model. It is gaining a crucial capability.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">There are no server components, no <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">'use client'</code> or <code class="bg-gray-200/60 dark:bg-gray-800 text-[var(--code-foreground)] [a_&amp;]:text-inherit [:is(h1,h2,h3,h4,h5,h6)_&amp;]:text-inherit mx-[0.1em] px-[0.25em] py-[0.05em] rounded-xs text-inline-code font-code wrap-anywhere" data-foldkit-identity="1cbc8656d30d3335">'use server'</code> boundaries, and no plan for them: the view is one function of one Model. Server rendering changes where the first paint comes from, not how you write applications.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Scaffold a Foldkit SSR application:</p><div data-pagefind-ignore="" class="relative min-w-0 mt-6 mb-6" data-foldkit-identity="dc9422a13b784eb0"><div class="text-sm"><pre class="shiki shiki-themes 0x96f-light 0x96f-dark" style="background-color:#eeedf2;--shiki-dark-bg:#1c1a20;color:#403d4a;--shiki-dark:#E0DEE6" tabindex="0"><code><span class="line"><span style="color:#6B5CB8;--shiki-dark:#A093E2">npx</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> create-foldkit-app@latest</span><span style="color:#0E7490;--shiki-dark:#49CAE4"> --rendering</span><span style="color:#3D6B06;--shiki-dark:#BCDF59"> ssr</span></span></code></pre></div><div class="code-embed-copy absolute top-2 right-2" data-foldkit-identity="8da9fb08f546f30b"><span role="status" aria-live="polite" class="sr-only"></span><button aria-label="Copy create a server-rendered Foldkit project to clipboard" class="p-2 rounded transition cursor-pointer border border-gray-300 dark:border-gray-700/50 bg-[var(--code-background)] text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700/30"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5" data-foldkit-identity="cf52cab577a864a4"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"></path></svg></button></div></div><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Check it out and let me know what breaks. And don't be a stranger!</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Devin</p></div></article>]]></content:encoded>
</item>
<item>
  <title>Dispatch 1</title>
  <link>https://foldkit.dev/blog/dispatch-1</link>
  <guid>https://foldkit.dev/blog/dispatch-1</guid>
  <description>APS-4 is on the fritz again.</description>
  <pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate>
  <enclosure url="https://foldkit.dev/blog/dispatch-1/cover.webp" length="23100" type="image/webp" />
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><div class="relative overflow-hidden rounded-lg mb-8" data-foldkit-identity="80a2c6e532125e5c"><div class="absolute inset-0 bg-gray-200 dark:bg-gray-800 animate-pulse"></div><img width="1600" height="1067" class="relative w-full h-auto" src="https://foldkit.dev/blog/dispatch-1/cover.webp" alt="Yellow and coral triangles glide toward a huge dark planet across a teal sky, past dashed flight paths and a small green moon, with bands of green, yellow, and gray along the left edge."></div><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="dispatch-1" data-foldkit-identity="89d40481a105ec00">Dispatch 1</h1><p class="text-gray-500 dark:text-gray-400">August 11, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><em data-foldkit-identity="dbce66070d77e202">Beep. Beep. Beep.</em></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">APS-4 is on the fritz again.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Time to twiddle my thumbs.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Can you really blame me?</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I’ve done the math. My job is <em data-foldkit-identity="dbce66070d77e202">oversight</em>. Manual manipulation results in glutamate accumulation. Oversight quality suffers. The mission suffers. Therefore, I don’t do manual. QED.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Besides, I don’t pine for the good old days.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">APS-4 does the wrong thing sometimes. It doesn’t matter.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Mostly, it does what I want. It does what I <em data-foldkit-identity="dbce66070d77e202">don’t know I want</em>.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Every outage I realize how little I ever knew.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><em data-foldkit-identity="dbce66070d77e202">Beep. Beep. Beep.</em></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Good time to check telemetry.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Day 417. 1,546 logs transmitted. 184 supply drops. 775 commendations. 9,341 recent supply requisitions. Progress unknown. Mission ambiguous. I don’t know what these numbers mean.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I feel itchy. There’s work to be done. Reports don’t cease when lunar base servers go down.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">But I’m past the point of commanding this thing with my own two hands. Not in this asteroid field.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">APS-4 has 10,000 hands. Each hand has 10,000 fingers. Even its fingers have fingers.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I’m not proud enough to cosplay as a billion fingers.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><em data-foldkit-identity="dbce66070d77e202">Beep. Beep. Beep.</em></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I do miss driving stick. I was a decent pilot.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Sometimes, when the radar is clear, I kill the uplink, just to remember what it was like. I probably hand-grind my coffee for the same reason.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">At least they haven’t made autopilot autopilot yet.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><em data-foldkit-identity="dbce66070d77e202">Beep. Beep. Beep.</em></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I converse with APS-4 more than I would like. It is effective and exhausting.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">I receive daily transmissions from fellow astronauts. I can make out their ships in the distance. Their messages are wonderful. They are so <em data-foldkit-identity="dbce66070d77e202">human</em>.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">In these quiet moments, I wonder if—</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50"><em data-foldkit-identity="dbce66070d77e202">We sincerely apologize for the disruption to your service. APS-4 access is restored. State your request.</em></p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">APS-4, dispatch mission log.</p></div></article>]]></content:encoded>
</item>
<item>
  <title>Foldkit Has a Blog Now</title>
  <link>https://foldkit.dev/blog/introducing-the-foldkit-blog</link>
  <guid>https://foldkit.dev/blog/introducing-the-foldkit-blog</guid>
  <description>The Foldkit blog now exists.</description>
  <pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate>
  <content:encoded><![CDATA[<article data-foldkit-identity="76a96447eaadcc72"><header class="mb-8"><h1 data-pagefind-meta="section" class="font-heading font-book text-page-title md:text-page-title-wide leading-tight tracking-tight text-gray-900 dark:text-white mb-3" id="introducing-the-foldkit-blog" data-foldkit-identity="89d40481a105ec00">Foldkit Has a Blog Now</h1><p class="text-gray-500 dark:text-gray-400">August 1, 2026 · Devin Jameson</p></header><div data-foldkit-identity="8fed9e3a5e2e9719"><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">Hey, Devin here. Foldkit has a blog now.</p><p class="mb-5 leading-7" data-foldkit-identity="ab595b796d113f50">This website is made with Foldkit and the blog uses <a class="link-accent" href="https://github.com/foldkit/foldkit/blob/main/packages/markdown/README.md" data-foldkit-identity="b9c1739f4bc7044a">@foldkit/markdown</a>.</p></div></article>]]></content:encoded>
</item>
</channel>
</rss>