<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikhil Verma</title>
    <description>The latest articles on DEV Community by Nikhil Verma (@nikhilverma).</description>
    <link>https://dev.to/nikhilverma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F199566%2Ff85ab586-cad0-4deb-b243-98fba72ac190.jpg</url>
      <title>DEV Community: Nikhil Verma</title>
      <link>https://dev.to/nikhilverma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikhilverma"/>
    <language>en</language>
    <item>
      <title>Building a harness that makes a small LLM reliable</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/nikhilverma/building-a-harness-that-makes-a-small-llm-reliable-5ca9</link>
      <guid>https://dev.to/nikhilverma/building-a-harness-that-makes-a-small-llm-reliable-5ca9</guid>
      <description>&lt;p&gt;When I started building a multi-turn agent that could actually &lt;em&gt;do&lt;/em&gt; things — search records, update a status, link a piece of evidence, the usual kit — my first instinct was the one I think everyone has: reach for the biggest, smartest model and hope it behaves.&lt;/p&gt;

&lt;p&gt;I ended up doing nearly the opposite. The agent I shipped runs on Haiku 3 — small, cheap, fast, and on its own absolutely not what you'd call a careful reasoner. The reliability didn't come from the model. It came from the scaffolding I built around it. And that shift — from "make the model smarter" to "make the harness stronger" — is honestly the most useful thing I've picked up about getting agents to work.&lt;/p&gt;

&lt;p&gt;Let me walk you through two pieces of that harness, because they did most of the heavy lifting. First, though, the little incident that kicked it all off.&lt;/p&gt;

&lt;p&gt;I was scrolling back through a long session, looking at tool-call errors. (You should be watching those, by the way — a model retrying its own failed tool calls is normal, but a &lt;em&gt;pattern&lt;/em&gt; of failures usually means a tool is confusing or a contract is too loose.) And I spotted a few calls hitting IDs that didn't exist. The IDs looked perfect: flawless, textbook UUIDs. Right shape, right everything. They just... weren't real.&lt;/p&gt;

&lt;p&gt;The model made them up. An LLM loves to match patterns, and Haiku had watched hundreds of UUIDs stream past in its context, absorbed the shape, and somewhere between "search for items" and "mark this one done" it confidently produced a plausible fake.&lt;/p&gt;

&lt;p&gt;Now, the lazy fix is "use a better model" — a bigger model hallucinates less. But that's the wrong lesson, and I want to spend a paragraph on why, because it shapes everything below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I got wrong for too long: the harness is the lever, not the model
&lt;/h2&gt;

&lt;p&gt;It's so tempting to treat reliability as something you buy from the model. Agent flaky? Upgrade. And it sort of works — partly, and expensively. But a frontier model with no guardrails still invents IDs and still skips steps; it just does it rarely enough that you stop noticing, which is arguably worse.&lt;/p&gt;

&lt;p&gt;What actually moved the needle for me was flipping the assumption: assume the model &lt;em&gt;will&lt;/em&gt; get things wrong — early, often, in daft ways — and build a harness that makes those mistakes either flat-out impossible or automatically recoverable. Do that, and a lovely thing happens: the bar for how clever the model has to be drops through the floor. A small model on a strong harness beats a big model on a weak one, and it costs a fraction as much per turn.&lt;/p&gt;

&lt;p&gt;So the two patterns below aren't "prompt it better." They're rails the model can't leave even if it wants to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Raw IDs are basically a loaded gun
&lt;/h2&gt;

&lt;p&gt;If you put raw UUIDs into the model's context, sooner or later it'll hand raw UUIDs back. Some real, some invented, and from the outside — just staring at the text — there's no reliable way to tell which is which. The smaller the model, the sooner this happens.&lt;/p&gt;

&lt;p&gt;You can &lt;em&gt;ask&lt;/em&gt; nicely, of course — "only use IDs I've actually given you" — but a prompt is a polite request, not a contract. Give a model a long context, a bit of instruction-following pressure, a small distraction, and it'll cheerfully forget. And there's no feedback loop: it has no idea the ID was wrong until your database has already either rejected it or, on a bad day, quietly swallowed it.&lt;/p&gt;

&lt;p&gt;I dug into the &lt;em&gt;why&lt;/em&gt; of this a while back — how models fabricate IDs and a couple of ways to stop it — in &lt;a href="https://dev.to/blog/llms-unreliable-narrators-uuid-hallucination/"&gt;a separate post on UUID hallucination&lt;/a&gt;. The short version: enum constraints work nicely for tightly-controlled flows, and &lt;em&gt;token aliasing&lt;/em&gt; (swapping UUIDs for simple &lt;code&gt;ITEM-1&lt;/code&gt;/&lt;code&gt;ITEM-2&lt;/code&gt; handles) works for multi-turn agents. That post is the background, if it's useful. What I want to get into here is what token aliasing has to grow into once a small model is allowed to actually &lt;em&gt;write&lt;/em&gt; to your database — and how it becomes one leg of the harness.&lt;/p&gt;

&lt;p&gt;Either way, the fix isn't to ask harder. It's to make raw IDs invisible to the model in the first place. If it never sees a UUID, it can't parrot one back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: the ref proxy
&lt;/h2&gt;

&lt;p&gt;The idea is dead simple. Every UUID that leaves your system on its way to the model gets swapped for a short, session-scoped nickname first. The model only ever sees things like &lt;code&gt;ref_A3k9Xp2Q&lt;/code&gt;. Your database only ever sees real UUIDs. A little registry sits in the middle and does the translation.&lt;/p&gt;

&lt;p&gt;Here's the shape of that registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EntityRefRegistry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ref token → { entityType, entityId, createdAt, lastSeenAt }&lt;/span&gt;
  &lt;span class="nl"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EntityRefRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// "type:uuid" → ref token (reverse lookup)&lt;/span&gt;
  &lt;span class="nl"&gt;entityToRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I park this as a durable JSONB column on the chat thread row. It survives across turns, so the same entity always gets the same nickname for the whole session — no confusion for a model that's already easily confused.&lt;/p&gt;

&lt;p&gt;The proxy works both ways. On the way &lt;em&gt;out&lt;/em&gt; (system → model): any time your tools hand back data, a &lt;code&gt;proxyForModel&lt;/code&gt; pass walks the output, finds every UUID-shaped string, and swaps it for a ref token — minting a new registry entry if it hasn't seen that entity before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxyForModel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntityRefRegistry&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntityRefRegistry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// walk value, replace UUIDs with ref_* tokens&lt;/span&gt;
  &lt;span class="c1"&gt;// registry is append-only — same UUID always gets same token&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the way &lt;em&gt;in&lt;/em&gt; (model → system): when the model calls a tool with a &lt;code&gt;ref_*&lt;/code&gt; value, a &lt;code&gt;proxyForSystem&lt;/code&gt; pass turns it back into the real UUID before your handler ever lays eyes on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;proxyForSystem&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntityRefRegistry&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;unresolvedRefs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;rawUuids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice it hands back two lists rather than throwing a tantrum. &lt;code&gt;unresolvedRefs&lt;/code&gt; catches refs the model invented that aren't in the registry. &lt;code&gt;rawUuids&lt;/code&gt; catches the times it bypassed the whole scheme and pasted a real UUID straight in — which happens more than you'd hope, especially early on when some old prompt or context snippet leaked raw IDs in before the proxy was watching the door.&lt;/p&gt;

&lt;p&gt;The tool wrapper checks both lists before it runs anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;systemInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proxyForSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;registryRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unresolvedRefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;toolInputError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Unknown model reference(s): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;systemInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unresolvedRefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;` Use a ref returned earlier in this chat, or search again for a fresh ref.`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawUuids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;toolInputError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Raw UUIDs are not accepted: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;systemInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawUuids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;` Use the corresponding ref from earlier in this chat.`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Outbound: swap UUIDs in result for refs&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proxyForModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registryRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;registryRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;modelResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;modelResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the bit that matters most: these are &lt;em&gt;recoverable&lt;/em&gt; errors. The model gets to read them and try again, in the same conversation, without anything blowing up. This is how the harness teaches a weak model on the fly — not by being cleverer, but by catching the mistake and handing back a specific, actionable correction.&lt;/p&gt;

&lt;p&gt;The simplest way is to &lt;strong&gt;return&lt;/strong&gt; a structured error as the tool's result instead of the normal output. &lt;code&gt;toolInputError&lt;/code&gt; here is just a small helper of mine that returns a plain object with a message in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toolInputError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model reads that like any other tool output, goes "ah, my mistake," and tries again. It works purely because the &lt;em&gt;message text&lt;/em&gt; spells out what to do next — no magic, the model is just reading English. Which means, for a small model, the wording of that error is part of your prompt engineering. "Invalid input" gets you nowhere; "Unknown ref ref_Q7zXm1 — search again to get a fresh one" gets you a correct retry. Be specific.&lt;/p&gt;

&lt;p&gt;(You can also achieve the same thing by &lt;em&gt;throwing&lt;/em&gt; and letting your agent loop catch the error and feed it back as the next tool result — whichever your framework makes easiest. The one myth worth killing: a thrown tool error doesn't have to abort the turn. Whether it does is up to how your loop is wired, not some law of nature.)&lt;/p&gt;

&lt;p&gt;And the schemas get rewritten before the model ever sees them, too. Any &lt;code&gt;{ type: "string", format: "uuid" }&lt;/code&gt; field becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^ref_[A-Za-z0-9]{6,12}$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Use the model-visible ref_* value from chat/page context. Do not provide a raw UUID."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worth being honest about what this does, though: that schema is a &lt;em&gt;hint&lt;/em&gt;, not a fence. In plain JSON Schema, &lt;code&gt;format&lt;/code&gt; and &lt;code&gt;pattern&lt;/code&gt; are annotations — the model treats them as guidance, and a vanilla validator won't necessarily enforce them. The actual enforcement is my proxy layer rejecting anything that isn't a genuine ref. So the schema tells the model what good looks like; the harness is the thing that holds the line. Belt and braces — which is the whole game when the model is small.&lt;/p&gt;

&lt;p&gt;The entire tool set gets wrapped in a single call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modelVisibleTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrapToolSetWithRefProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yourTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registryRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your existing tools don't change at all. The proxy is transparent to them — which is the point. The harness should be something you bolt &lt;em&gt;around&lt;/em&gt; your tools, not something you have to rewrite them for.&lt;/p&gt;

&lt;p&gt;Bonus prize: the registry doubles as a complete audit trail. Every entity the model touched, when it first appeared, when it was last referenced — all sitting in the thread row. For a system that has to answer "what did the agent actually do," that fell out for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: mandatory tool contracts
&lt;/h2&gt;

&lt;p&gt;The second failure mode was sneakier, and it's classic small-model behaviour. The model would breeze through a whole workflow and then skip the one step that actually &lt;em&gt;commits the result&lt;/em&gt; — it'd narrate what it found, lay out a lovely conclusion, and then end the turn with prose instead of calling the tool that records the verdict.&lt;/p&gt;

&lt;p&gt;Prompting did not reliably fix it. "You MUST call &lt;code&gt;submit_verdict&lt;/code&gt; before ending" helps a bit, but it's a vibe, not a guarantee, and the smaller the model the more of a vibe it is. Long multi-step runs drift from earlier instructions. Context compaction eats your constraints. So instead of nagging, I made the &lt;code&gt;endTurn&lt;/code&gt; tool &lt;em&gt;aware&lt;/em&gt; of what has to have happened before it's allowed to succeed.&lt;/p&gt;

&lt;p&gt;Workflows declare their required tools right in the frontmatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;control-assessment&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Control Assessment&lt;/span&gt;
&lt;span class="na"&gt;mandatory_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;search_evidence&lt;/span&gt;
    &lt;span class="na"&gt;minCalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
    &lt;span class="na"&gt;onTooFew&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;least&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;times&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;concluding."&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;submit_assessment&lt;/span&gt;
    &lt;span class="na"&gt;onMissing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;call&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;submit_assessment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;before&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ending&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;turn."&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner normalizes these into rules and attaches them to the conversation. Before the stream even starts, it also replays the prior message history to count any mandatory calls that already happened in earlier turns — so a tool called back in turn 2 still counts when you're in turn 5.&lt;/p&gt;

&lt;p&gt;Enforcement comes in two layers. First, every mandatory tool's execute gets wrapped with a counter: a &lt;em&gt;successful&lt;/em&gt; call ticks it up, an errored call (including the ref-proxy rejections above) does not. A failed attempt doesn't get to check the box.&lt;/p&gt;

&lt;p&gt;Second, the &lt;code&gt;endTurn&lt;/code&gt; tool gets a bouncer on the door:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endTurn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateMandatoryToolCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counts&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;violation&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;violation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endTurn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mandatoryToolErrorResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mandatoryToolErrorResult&lt;/code&gt; returns a structured (again, &lt;em&gt;returned&lt;/em&gt;, not thrown) error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You cannot end this turn yet. Required tool &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;submit_assessment&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; has not been called...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;missingMandatoryTools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit_assessment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;minCalls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model reads it, realises it hasn't held up its end, and calls the missing tool. With Haiku the self-correction rate here was genuinely high — because the message is specific enough that there's no thinking required, just following.&lt;/p&gt;

&lt;p&gt;And there's a second lock on the same door. The stop condition itself is gated, so even a stray &lt;code&gt;endTurn&lt;/code&gt; won't actually halt the loop. It's a small enough check to just write by hand — whatever your agent loop is, it almost certainly calls some "should I stop now?" predicate after each step. Mine only lets the loop stop once the model has called &lt;code&gt;endTurn&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; every mandatory tool has hit its count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Called by the agent loop after each step to decide whether to stop.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;shouldStop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// hard safety cap, no matter what&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calledEndTurn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;endTurn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;calledEndTurn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// not trying to end — keep going&lt;/span&gt;

  &lt;span class="c1"&gt;// The model wants to end. Only allow it if the contract is satisfied.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;countToolCalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validateMandatoryToolCounts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model somehow sneaks &lt;code&gt;endTurn&lt;/code&gt; past the wrapper, this just refuses to fire. The stream keeps going and it gets another crack at the contract. It isn't getting out until the work is done.&lt;/p&gt;

&lt;p&gt;And for the genuinely grim cases — a run that dies on the step limit or a network blip without ever satisfying the contract — the runner checks on finish and can replay the turn, this time forcing the very next step to call the missing tool directly (most agent loops let you pin which tool runs next). That's the last line of defence: the harness physically walking the model to the till.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thread running through both: recover, don't demand perfection
&lt;/h2&gt;

&lt;p&gt;If you squint, both patterns are the same move. Neither one assumes the model is good. Both assume it'll fumble and build the catch around it.&lt;/p&gt;

&lt;p&gt;The ref proxy says: the model &lt;em&gt;cannot&lt;/em&gt; produce a valid raw ID, and if it tries, it gets a precise nudge and another go. The mandatory contract says: the model &lt;em&gt;cannot&lt;/em&gt; end before the work is committed, and if it tries, it gets a precise nudge and another go. The capability that used to live in the model — "remember to use real IDs," "remember to commit" — now lives in the harness as something structural. The model just has to follow rails, and following rails is something even a small model is pretty good at.&lt;/p&gt;

&lt;p&gt;This is also why the error wording isn't an afterthought. For a small model the recoverable error &lt;em&gt;is&lt;/em&gt; the teaching signal. "Invalid input" gets you nowhere; "Unknown ref ref_Q7zXm1 — search again to get a fresh one" gets you a correct retry. You're not arguing with the model, you're handing it the next instruction at exactly the moment it's listening hardest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed — and the bit that surprised me
&lt;/h2&gt;

&lt;p&gt;Before: occasional hallucinated IDs slipping into the database, semi-regular turns that fizzled out in prose instead of a committed result, behaviour that wobbled with context length. Death by a thousand "huh, that's odd"s.&lt;/p&gt;

&lt;p&gt;After: hallucinated IDs became &lt;em&gt;structurally impossible&lt;/em&gt; — there's no UUID surface left to hallucinate against. Required-tool completion became something I could &lt;em&gt;verify&lt;/em&gt; instead of hope for — not "the model said it was done" but "the counter hit the minimum." And the audit trail came free.&lt;/p&gt;

&lt;p&gt;The genuinely surprising part was the cost side. I'd assumed reliable agent behaviour meant paying for a big model on every turn. A small, cheap model — Haiku 3 — carried the whole thing once the rails were in place, at a fraction of the per-turn cost and noticeably faster. The harness was a one-time build; the savings are every single request, forever.&lt;/p&gt;

&lt;p&gt;The same harness makes a bigger model better too. Guardrails help across the board. The win isn't "make the frontier model slightly more reliable." It's that you stop &lt;em&gt;needing&lt;/em&gt; the frontier model for a big chunk of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;I spent a long time treating the model as the variable I could turn up when things got unreliable. The thing I'd tell my earlier self is: turn up the harness instead.&lt;/p&gt;

&lt;p&gt;There are two failure modes people lump together when they say "my agent won't follow instructions." One is &lt;em&gt;behavioural&lt;/em&gt; — the model drifts, gets muddled, picks the wrong priority. That one genuinely is a prompting problem, and a bigger model genuinely helps. The other is &lt;em&gt;structural&lt;/em&gt; — your "enforcement" is just text in a prompt that the model can and eventually will ignore. No amount of bold &lt;strong&gt;MUST&lt;/strong&gt; saves you there, and no model is big enough to fully trust.&lt;/p&gt;

&lt;p&gt;For the structural stuff you need structural fixes, and the nice surprise is that once you've built them, the model underneath can be small and cheap. Make hallucinated IDs invalid by construction. Make skipping a step mechanically impossible. Make every mistake recoverable with a clear nudge. Do that, and a model like Haiku 3 will quietly do work you assumed needed something four times the price.&lt;/p&gt;

&lt;p&gt;The mental model I use now: if the correctness of a workflow depends on the model &lt;em&gt;choosing&lt;/em&gt; to do the right thing, stop and ask whether the harness can make that choice for it. It usually can. And it's almost always cheaper than reaching for a smarter model to paper over the gap.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>aiagents</category>
      <category>aiengineering</category>
      <category>agentreliability</category>
    </item>
    <item>
      <title>Rate limiting AI APIs across distributed workers</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/nikhilverma/rate-limiting-ai-apis-across-distributed-workers-1ifl</link>
      <guid>https://dev.to/nikhilverma/rate-limiting-ai-apis-across-distributed-workers-1ifl</guid>
      <description>&lt;p&gt;So picture this. The system is throwing Anthropic 429s every two or three minutes during big processing runs. Fine — expected, honestly. We were processing thousands of documents in parallel across a fleet of Temporal workers, so of course we were leaning on the rate limit. What was &lt;em&gt;not&lt;/em&gt; expected was that the 429s got &lt;strong&gt;worse&lt;/strong&gt; after we added retry logic. You add the thing that's supposed to help, and it makes everything angrier. Cool stuff.&lt;/p&gt;

&lt;p&gt;We'd implemented exponential backoff with a max of 120 seconds, by the time most of those long backoffs fired, the quota they were waiting on had already come back. We were manufacturing dead air on top of the rate-limit problem and then patting ourselves on the back for it.&lt;/p&gt;

&lt;p&gt;Let me walk you through the three-layer pattern that actually fixed it. But first I have to clear up a misconception I had, because it's the whole reason the backoff was wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing about TPM limits that I got wrong
&lt;/h2&gt;

&lt;p&gt;I used to picture token-per-minute limits like a parking meter: the clock ticks over at 60 seconds and &lt;em&gt;bang&lt;/em&gt;, the whole allowance resets to full. So my mental model was "if I get a 429, just wait until the next reset and I'm golden." Backing off past that reset point is obviously pointless, right?&lt;/p&gt;

&lt;p&gt;Turns out that's not how it works. A rate limit doesn't snap back all at once — it replenishes &lt;em&gt;gradually&lt;/em&gt;. Whatever a given provider uses under the hood, a token bucket that refills at a steady rate or a sliding window that lets old usage age out, the effect is the same: your headroom climbs back up continuously across the window rather than in a single tick at the top of the minute. There's no parking-meter moment to wait for. So when you've completely drained your quota, getting back to full takes roughly the length of the window — call it a minute — and it happens a little at a time the whole way there, not in one lump at the end. (Most providers document exactly this; Anthropic, for instance, &lt;a href="https://platform.claude.com/docs/en/api/rate-limits" rel="noopener noreferrer"&gt;describes its limit as a continuously replenished token bucket&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;So why does that change anything? Because the practical upshot is almost the same but the &lt;em&gt;reason&lt;/em&gt; is completely different. If you've fully drained the bucket, it refills back to maximum over roughly a minute. So the useful ceiling on a backoff is about 60 seconds — not because a window "resets," but because after ~60s of trickle the bucket has recovered all the headroom it's ever going to give you. Wait longer than that and you're just standing there while a full bucket waits for you to do something with it.&lt;/p&gt;

&lt;p&gt;Standard exponential backoff doesn't know any of this. It just keeps doubling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wait = base * 2^attempt + jitter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a base of 10 seconds, attempt 4 lands you at ~160 seconds of waiting. But the bucket finished refilling about 100 seconds ago. You're backing off from a problem that no longer exists. Every second past 60 is pure waste.&lt;/p&gt;

&lt;p&gt;So cap it at the window's worth of refill — 60 seconds — and stop there. Not 2 minutes, not 5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_BASE_BACKOFF_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_MAX_BACKOFF_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ~one bucket-refill's worth&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateBackoffMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exponential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_BASE_BACKOFF_MS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;capped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_MAX_BACKOFF_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jitterRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capped&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_BASE_BACKOFF_MS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;RATE_LIMIT_BASE_BACKOFF_MS&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jitterRange&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that clicked, the rest of the architecture kind of fell out on its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1: tell everyone about the 429, immediately (Redis)
&lt;/h2&gt;

&lt;p&gt;Here's where it gets fun with multiple workers. Per-worker backoff is useless on its own when you've got a whole pool hammering the same endpoint. One worker gets a 429 and starts politely backing off — meanwhile the other nine are still firing away, blissfully ignorant. They each catch their own 429, each start their own little timer, and the whole pool spends the next few minutes flailing before it settles. It's like everyone in the pub trying to get to the bar at once and nobody noticing it's already three-deep.&lt;/p&gt;

&lt;p&gt;The fix is to make a 429 &lt;em&gt;everybody's&lt;/em&gt; problem the instant it happens. When any worker hits a rate limit, it writes a pause state to Redis. And every worker checks that state before every single AI call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// When we get a 429&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`rate_limit:pause:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;modelKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pauseUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;waitMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lastThrottleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="c1"&gt;// 5-minute TTL; auto-expires if workers die&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Before every AI call&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pauseState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`rate_limit:pause:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;modelKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pauseState&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;parsedState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pauseUntil&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsedState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pauseUntil&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail that matters more than it looks: namespace the key by &lt;code&gt;modelKey&lt;/code&gt; (&lt;code&gt;provider:model&lt;/code&gt;). A 429 on &lt;code&gt;provider-a:model-x&lt;/code&gt; has no business pausing work on &lt;code&gt;provider-b:model-y&lt;/code&gt; — those are completely separate buckets. Pause everything every time one model throttles and you've just tanked your throughput for no reason.&lt;/p&gt;

&lt;p&gt;And that 5-minute TTL is the safety net. If a worker keels over mid-flight with a pause state still written, the TTL makes sure the rest of the pool isn't held hostage forever by a ghost.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: jitter on the way back, or you'll do it all again
&lt;/h2&gt;

&lt;p&gt;Right, so here's the sneaky one that survives even after you've added the cross-worker pause. The pause expires at the &lt;em&gt;exact same instant&lt;/em&gt; for everybody. So all the workers wake up together, all check the gate together, all see it's clear together, and all fire together — perfectly recreating the original spike that caused the 429 in the first place. You've built a thundering herd and handed it a starting pistol.&lt;/p&gt;

&lt;p&gt;The fix is to add a bit of random jitter on each worker's wake-up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remainingPause&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pauseState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pauseUntil&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0-30s&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remainingPause&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;jitter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the wake-ups smear out across a 30-second window. Worker A pops up at T+0 and fires one request, worker B at T+7s, and so on down the line. By the time the whole pool is back in business, that initial burst has been staggered into something the provider can actually stomach. Bog-standard thundering-herd mitigation — but so easy to forget, because you add the cross-worker pause, feel clever, and assume the problem's solved.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3: kill the client library's own retry (this was the villain)
&lt;/h2&gt;

&lt;p&gt;This was &lt;em&gt;the&lt;/em&gt; bug. The one that made everything worse the moment we added rate limiting.&lt;/p&gt;

&lt;p&gt;Almost every LLM client library has retry logic baked in. By default, when a call gets a 429, the client quietly retries on your behalf with its own exponential backoff — often two or three extra attempts you might not even realise are happening. Now stack that on top of your Temporal activity retries &lt;em&gt;and&lt;/em&gt; your own &lt;code&gt;withRateLimitRetry&lt;/code&gt; wrapper, and congratulations: you've got three independent retry loops running at once, none of them aware of the others.&lt;/p&gt;

&lt;p&gt;The interaction is genuinely nasty. Temporal fires an activity. Inside it, the client library eats a 429 and starts its own retry, sleeping a while. Meanwhile your &lt;code&gt;withRateLimitRetry&lt;/code&gt; wrapper has &lt;em&gt;also&lt;/em&gt; clocked the 429 and started &lt;em&gt;its&lt;/em&gt; own sleep. When the client's retry eventually fires, it goes back through &lt;code&gt;withRateLimitRetry&lt;/code&gt;, which records &lt;em&gt;another&lt;/em&gt; 429 and sets &lt;em&gt;another&lt;/em&gt; pause. It cascades. And the worst part is the logs make it look like the backoff is working a treat, when really it's just multiplying your wait times behind your back.&lt;/p&gt;

&lt;p&gt;So turn the client's built-in retry off and let a single layer own the whole thing. Most libraries expose this as a max-retries option — set it to zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Whatever your LLM client is, find its retry knob and switch it off,&lt;/span&gt;
&lt;span class="c1"&gt;// so the outer layer (Temporal + withRateLimitRetry) is the only thing retrying.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One retry layer that actually understands how the quota refills will always beat three that are each guessing in the dark.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going proactive: token accounting with a Redis ZSET
&lt;/h2&gt;

&lt;p&gt;Everything above is &lt;em&gt;reactive&lt;/em&gt; — it stops the bleeding after a 429. But by the time you've got a 429, you've already paid for it: a failed network call, wasted tokens, and a provider that's now mildly cross with you. The proactive layer's job is to stop the 429 ever happening.&lt;/p&gt;

&lt;p&gt;The trick is a sorted set in Redis, keyed by &lt;code&gt;provider:model&lt;/code&gt;. Every completed call drops its token count in as a member, scored by the timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zadd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`token_usage:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;modelKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reservationId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Evict entries older than 60 seconds&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;zremrangebyscore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before each call, you read the set, sum the tokens still in the last 60 seconds, and check against your TPM ceiling. Getting close to the edge? Wait for the oldest entries to age out instead of lobbing in a call that's just going to 429. (This proactive accounting earns its keep doubly with Anthropic, by the way, because the bucket also enforces short bursts — a "60 requests per minute" limit can show up as "one request per second" — and it doesn't love sharp usage ramps. Smoothing your own flow keeps you on its good side.)&lt;/p&gt;

&lt;p&gt;Now, the detail that'll bite you if you skip it: &lt;strong&gt;reserve upfront, commit afterward.&lt;/strong&gt; When a bunch of workers are calling concurrently, they each read the current usage and each see lovely headroom. If they all fire at once, the real usage is the &lt;em&gt;sum&lt;/em&gt; of everyone's reservations — not the number any one of them read. So the read-then-write has to be atomic. A tiny Lua script does it in a single round-trip (and Redis runs Lua atomically — nothing else gets to sneak in mid-script):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Atomic: inspect bucket, reserve in one shot or reject&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;currentTpm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sumBucketEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;windowStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;currentTpm&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;reserveTokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tpmLimit&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'tpm'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retryAfterSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deficit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ZADD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reservationId&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;reserveTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the call finishes, commit the &lt;em&gt;actual&lt;/em&gt; usage to replace the reservation. Models almost always generate fewer tokens than &lt;code&gt;max_tokens&lt;/code&gt;, so committing the real number hands the unused budget back to the shared pool. Be greedy on the reservation, honest on the commit.&lt;/p&gt;

&lt;p&gt;This is the layer that keeps the system from ever really needing the reactive pause under normal load. The 429 handler becomes the fire alarm, not the day-to-day traffic cop.&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole picture
&lt;/h2&gt;

&lt;p&gt;Three layers, working together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redis ZSET sliding window&lt;/strong&gt; — proactive token accounting with atomic reservation. Workers see each other's consumption &lt;em&gt;before&lt;/em&gt; they fire. Stops most 429s ever happening.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-worker pause state&lt;/strong&gt; — the reactive 429 handler that writes to Redis the instant it's hit. The whole pool backs off together, and the backoff caps at one bucket-refill's worth (~60s) instead of some open-ended exponential fantasy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One retry layer&lt;/strong&gt; — SDK retry switched off. Temporal's activity retry plus the &lt;code&gt;withRateLimitRetry&lt;/code&gt; wrapper own the semantics. No more three independent loops tripping over each other.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;End result: 429s went from a regular feature of every big batch run to a rare event the system shrugs off gracefully when it does show up.&lt;/p&gt;

&lt;p&gt;And the general lesson travels well beyond Temporal — BullMQ, Celery, your own hand-rolled queue, doesn't matter. The key fact is that TPM limits are &lt;em&gt;time-windowed&lt;/em&gt;, and time-windowed limits have a natural ceiling on how long backing off is even worth it. Build your retry ceiling around how long the bucket takes to refill, and make that state shared across all your workers, and you'll get further than any amount of lovingly hand-tuned exponential backoff coefficients ever will.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ratelimiting</category>
      <category>distributedsystems</category>
      <category>aiengineering</category>
    </item>
    <item>
      <title>Agents should be able to time travel</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Tue, 06 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/nikhilverma/agents-should-be-able-to-time-travel-1i73</link>
      <guid>https://dev.to/nikhilverma/agents-should-be-able-to-time-travel-1i73</guid>
      <description>&lt;p&gt;When I work on a tricky problem I often go down random directions. I explore, learn what I can, and bring the learnings back. And I remember not to go down there again. This exploration in my mind is similar to Age of Empires fog of war or slime mold exploring its surroundings and then finding the right path.&lt;/p&gt;

&lt;p&gt;AI agents could learn to benefit from doing the same, and they do for the most part, but they've got a bottleneck. They can't forget useless things. It's all appended in their context, or compacted when the context window is about to be reached. This doesn't help and can be improved.&lt;/p&gt;

&lt;p&gt;Right now there are a few ways agents deal with long context&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Summarization&lt;/strong&gt; Compress old messages into a summary. But summaries are lossy. You lose the reasoning chain the failed attempts, the "why" behind decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stores&lt;/strong&gt; Store stuff externally and retrieve when needed. But this is &lt;em&gt;passive&lt;/em&gt;. The agent doesn't control what's stored or when it's retrieved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just... keep everything&lt;/strong&gt; Until you hit the context limit and things get weird.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's also some cool research happening here. The &lt;a href="https://arxiv.org/abs/2303.11366" rel="noopener noreferrer"&gt;Reflexion framework&lt;/a&gt; lets agents reset their environment and try again, keeping self-reflections in memory. &lt;a href="https://arxiv.org/abs/2305.10601" rel="noopener noreferrer"&gt;Tree of Thoughts&lt;/a&gt; explores multiple reasoning paths. And there's recent work like &lt;a href="https://arxiv.org/abs/2510.12635" rel="noopener noreferrer"&gt;"Memory as Action"&lt;/a&gt; and &lt;a href="https://arxiv.org/abs/2508.00031" rel="noopener noreferrer"&gt;"Git Context Controller"&lt;/a&gt; that treat context management as something the agent should actively control.&lt;/p&gt;

&lt;p&gt;But they're either architectural changes (you need to rebuild your whole system) or they're too drastic (reset everything and start over).&lt;/p&gt;

&lt;h3&gt;
  
  
  history.go(-10, "Bruh this path was a dead end")
&lt;/h3&gt;

&lt;p&gt;What if we gave the agent a tool? Something dead simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ok I've learned enough here. Take this message as my learning, drop the last 20 messages, and let's continue.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. The agent decides when to use it. The agent decides what to keep, decides how far back to go.&lt;/p&gt;

&lt;p&gt;The important bit here is that the agent initiates this, not some external system watching token counts. The agent realizes "I've been spinning my wheels" or "I found what I needed" and actively chooses to clean up after itself. I think the smarter models like Opus 4.5 and GPT-5.2 will be able to do this well.&lt;/p&gt;

&lt;p&gt;I'm imagining a tool like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;time_travel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;learning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The API doesn't support batch operations, need to loop instead&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;steps_back&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="c1"&gt;// or go_to: "message_id_12345"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent calls this. The system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes the learning and injects it back as context (system prompt or user message)&lt;/li&gt;
&lt;li&gt;Drops the last N messages from the conversation&lt;/li&gt;
&lt;li&gt;Continues from there&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's this thing called &lt;code&gt;Physarum polycephalum&lt;/code&gt;. It's a slime mold. It doesn't have a brain, but it can solve mazes and find optimal paths between food sources. Researchers even used it to &lt;a href="https://www.science.org/doi/10.1126/science.1177894" rel="noopener noreferrer"&gt;recreate the Tokyo rail network&lt;/a&gt;. Here is a cool video of it in action:&lt;/p&gt;

&lt;p&gt;How does it do this? It explores everywhere at first. Then it withdraws from dead ends while reinforcing successful paths. It doesn't keep track of every failed path — it just stops going there.&lt;/p&gt;

&lt;p&gt;That's what I want for agents. Explore, learn, withdraw, reinforce. Not "append everything forever and hope the model figures it out."&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>LLMs as Unreliable Narrators: Dealing with UUID Hallucination</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/nikhilverma/llms-as-unreliable-narrators-dealing-with-uuid-hallucination-151e</link>
      <guid>https://dev.to/nikhilverma/llms-as-unreliable-narrators-dealing-with-uuid-hallucination-151e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jfbyb735uq9m3jm95af.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9jfbyb735uq9m3jm95af.png" alt="Robot struggling with UUIDs" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; LLMs make up identifiers (UUIDs) that don't exist in your data, which breaks your retrieval flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution 1:&lt;/strong&gt; Use enum constraints with readable identifiers when you control the workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solution 2:&lt;/strong&gt; Map UUIDs to simple tokens (ITEM-1, ITEM-2) for longer agent conversations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Way fewer hallucinations and more reliable ID references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You've spent weeks building a neat RAG (Retrieval-Augmented Generation) system. Your embeddings are sharp, your retrieval is fast, and you've got the LLM hooked up to search through your data. You test it out, and then... the model returns a UUID that doesn't exist in your system.&lt;/p&gt;

&lt;p&gt;This is one of those problems that catches you off guard because the LLM sounds &lt;em&gt;confident&lt;/em&gt;. It's not hedging or saying "I'm not sure." It's just making up an identifier like &lt;code&gt;a7f3c9e2-1b4d-4e8f-9c5d-2a8f7b9e3d1c&lt;/code&gt; with complete conviction.&lt;/p&gt;

&lt;p&gt;Smaller models (like mini/fast variants) tend to do this more often. They'll invent IDs to sound authoritative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem
&lt;/h2&gt;

&lt;p&gt;LLMs are pattern-matching machines. When you ask them to pick from a list of items using UUIDs, they see the format - that alphanumeric pattern with hyphens - and they try to reproduce it. But they're not really "remembering" your specific IDs. They're just generating something that &lt;em&gt;looks&lt;/em&gt; like a UUID, and half the time, it's completely made up.&lt;/p&gt;

&lt;p&gt;This creates problems downstream: you can't match outputs to stored objects, logging gets messy, and audit trails break. You end up with a bunch of "ID not found" errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1: Enums for Controlled Flows
&lt;/h2&gt;

&lt;p&gt;If you're building a structured, agentic workflow where you control the entire decision path, you have a powerful tool: &lt;strong&gt;structured outputs with enums&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple: instead of letting the LLM generate IDs freely, you constrain its response to only pick from valid options. Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper to normalize titles and avoid duplicates&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Build a list of valid choices from your data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validChoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;norm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Handle duplicate titles by adding a suffix&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Create a schema where the model must pick from an enum&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selectedTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validChoices&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]]),&lt;/span&gt;
  &lt;span class="na"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Cap verbosity&lt;/span&gt;
  &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Use your LLM's structured output feature&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you pass an enum constraint to the LLM, it can't hallucinate. The model knows it &lt;em&gt;must&lt;/em&gt; pick one of the valid options. If it tries to return something outside the enum, the parsing will fail and you can retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key trick:&lt;/strong&gt; Use simple, human-readable identifiers (like titles) in your enum, not UUIDs. UUIDs are harder for models to reproduce accurately because they're random. Titles are memorable patterns.&lt;/p&gt;

&lt;p&gt;Then verify the match server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedTitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Find the actual item by matching the normalized title&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;normalizeTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nf"&gt;normalizeTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedTitle&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;actualItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Invalid selection - add feedback and retry&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`That title wasn't in the list. Please pick from: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;validChoices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Retry with feedback (consider exponential backoff)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; Controlled flows where you're calling the LLM as part of a defined process. Think: multi-step decision trees, validation agents, classification pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Watch out for enum size - OpenAI caps schemas at &lt;a href="https://platform.openai.com/docs/guides/structured-outputs#limitations-on-enum-size" rel="noopener noreferrer"&gt;1000 total enum values, with character length limits for larger enums&lt;/a&gt;. If you're dealing with hundreds of items, you might want to do some top-N prefiltering or break it into chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: Deterministic Alias Tokens for Multi-Turn Flows
&lt;/h2&gt;

&lt;p&gt;But what if your LLM is having a longer conversation where you're not controlling every single prompt? The agent is exploring, reasoning, making its own calls. Enums get awkward here because the agent might reference the same ID across multiple turns, and rebuilding your enum every time is messy.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;token mapping&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;The idea: create a simple mapping from tricky identifiers (like UUIDs) to easy ones (like &lt;code&gt;ITEM-1&lt;/code&gt;, &lt;code&gt;ITEM-2&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create token maps at the start of your agent run&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idToToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenToId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tokenCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// When the agent needs to search or reference items,&lt;/span&gt;
&lt;span class="c1"&gt;// return tokens instead of real IDs&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mapItemsToTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reuse existing token if we've seen this item before&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;idToToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ITEM-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tokenCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;idToToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;tokenToId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Return the token, not the real UUID&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when the agent works with items, it's dealing with &lt;code&gt;ITEM-1&lt;/code&gt;, &lt;code&gt;ITEM-2&lt;/code&gt;, etc. These are way harder to make up because they're simple and predictable. The agent picks them up quickly and usually sticks with them.&lt;/p&gt;

&lt;p&gt;At the end, just decode the tokens back to real IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Helper for safer decoding&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decodeTokenOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenToId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown token: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedMatches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;agentResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;realId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenToId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;realId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Agent returned unknown token: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;realId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt; Tokens are simple and sequential. The model picks them up easily and reuses them instead of making up new IDs. You've basically given the agent a clean vocabulary to work with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use this:&lt;/strong&gt; Multi-turn conversations, exploratory agents, any flow where the LLM is doing its own thing and needs to reference items consistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; Every few turns, remind the model what tokens are active ("Active items: ITEM-1:titleA, ITEM-2:titleB...") to keep things fresh in longer chats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the Approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Enum Constraints&lt;/th&gt;
&lt;th&gt;Token Aliasing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (list mapping)&lt;/td&gt;
&lt;td&gt;Medium (bidirectional maps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prompt Size Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High for large lists&lt;/td&gt;
&lt;td&gt;Low (small tokens)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-Turn Stability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Requires rebuild each turn&lt;/td&gt;
&lt;td&gt;Naturally consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hallucination Prevention&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Complete (hard constraint)&lt;/td&gt;
&lt;td&gt;Very high (simple patterns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single-shot decisions&lt;/td&gt;
&lt;td&gt;Long conversations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure Mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parse error (retryable)&lt;/td&gt;
&lt;td&gt;Unknown token (logged)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Combining Both Strategies
&lt;/h2&gt;

&lt;p&gt;If you're building something more involved, you can mix both approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use tokens as your main interface&lt;/strong&gt; to keep hallucinations low&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add enum validation for critical decisions&lt;/strong&gt; as an extra safety net&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify everything server-side&lt;/strong&gt; before you commit the data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you layered protection: the LLM rarely makes mistakes (tokens are simple), and when it does, you catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;LLMs love to make up identifiers. They see the UUID pattern and just run with it. But you've got options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For structured flows:&lt;/strong&gt; Use enums to lock them into valid choices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For exploratory flows:&lt;/strong&gt; Give them simple tokens to work with instead of UUIDs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either approach cuts down hallucinations significantly. Pick whichever fits your setup better, and you'll save yourself a lot of debugging headaches.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>rag</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Moving from React to Vue? Here's what to expect</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Mon, 21 Aug 2023 14:33:26 +0000</pubDate>
      <link>https://dev.to/nikhilverma/switching-from-react-to-vue-heres-what-to-expect-5i8</link>
      <guid>https://dev.to/nikhilverma/switching-from-react-to-vue-heres-what-to-expect-5i8</guid>
      <description>&lt;p&gt;I moved from a React to a Vue project two years ago from a company change. If you are about to undergo a similar journey, this post will help.&lt;/p&gt;

&lt;p&gt;I will not cover the basic differences between them. I will instead attempt to explain the conceptual differences that I found when switching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embrace Vue's reactivity
&lt;/h2&gt;

&lt;p&gt;My favourite part about Vue is that you can &lt;strong&gt;simply&lt;/strong&gt; modify state. Vue will track the changes and update the computed properties and UI automatically. In React this is done explicitly via setters or using libraries which internally do the same.&lt;/p&gt;

&lt;p&gt;Before joining the Vue project I had read how mutating state is bad because you can't track where the changes are coming from. While this is a valid point, in practice a reasonable data organisation and development discipline makes it a non issue.&lt;/p&gt;

&lt;p&gt;The productivity gains from modifying a state property and &lt;strong&gt;knowing&lt;/strong&gt; that the UI will update automatically are incredible.&lt;/p&gt;

&lt;p&gt;This also integrates well with effects and computed properties. You don't need to pass dependencies in your effects/computed properties, it "just works".&lt;/p&gt;

&lt;p&gt;Here is an example code that works in Vue out of the box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* state.js */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* App.vue */&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count++&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Doing something similar in React will need a state management library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know your Vue version
&lt;/h2&gt;

&lt;p&gt;There is a big difference between Vue 2 and 3 experiences. With the latter being more polished and better supported in the modern web development experience.&lt;/p&gt;

&lt;p&gt;I have &lt;a href="https://dev.to/nikhilverma/from-vue-2-to-3-a-long-journey-58ff"&gt;written&lt;/a&gt; about the pain we went through when migrating from Vue 2 to 3.&lt;/p&gt;

&lt;p&gt;In React, you wouldn't have to worry about this as there haven't been major breaking API changes between version updates.&lt;/p&gt;

&lt;p&gt;Vue 2 will reach end of life support on &lt;a href="https://v2.vuejs.org/lts/"&gt;December 31st, 2023&lt;/a&gt;. My advice would be to plan a migration to Vue 3 as soon as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the core difference between Vue templates and JSX
&lt;/h2&gt;

&lt;p&gt;API differences aside, you must have a basic understanding of how Vue templates operate internally and how they differ from JSX.&lt;/p&gt;

&lt;p&gt;Vue templates are closer to HTML, JSX is closer to Javascript. (You might have heard the phrase "JSX is Javascript").&lt;/p&gt;

&lt;p&gt;In JSX you can refer to local variables, use Javascript expressions and use JSX inline with other JS code. &lt;/p&gt;

&lt;p&gt;In Vue you must define them inside a template block. They can only refer to values you have either provided in your component definition (options API) or present in your setup scope (composition API).&lt;/p&gt;

&lt;p&gt;You also can't easily split Vue templates into inline code like JSX. They must be defined in a separate SFC file or a string template inside a render function (which needs runtime compilation).&lt;/p&gt;

&lt;p&gt;An example of what I am talking about. Let's take two bits of Vue and React code. They basically behave in the same way. And let's compare their code (as you would write it) and their compiled output.&lt;/p&gt;

&lt;h3&gt;
  
  
  React
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HelloWorld&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HelloWorld&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HeaderAlt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloWorld&lt;/span&gt;
      &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vite + React&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{...{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alt-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;))}&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alt header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/HelloWorld&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  React (as compiled by Babel)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HelloWorld&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HelloWorld&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HeaderAlt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_jsx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jsxs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_jsxs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react/jsx-runtime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="cm"&gt;/*#__PURE__*/&lt;/span&gt;&lt;span class="nx"&gt;_jsxs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HelloWorld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vite + React&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="cm"&gt;/*#__PURE__*/&lt;/span&gt;&lt;span class="nx"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HeaderAlt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="cm"&gt;/*#__PURE__*/&lt;/span&gt;&lt;span class="nx"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="cm"&gt;/*#__PURE__*/&lt;/span&gt;&lt;span class="nx"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alt-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;
    &lt;span class="p"&gt;})),&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alt header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's look at a similar Vue code&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HelloWorld&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HelloWorld.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Header.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HeaderAlt.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamicAttribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alt-header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HelloWorld&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vite + Vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isAlternateHeader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="nx"&gt;dynamicAttribute&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;item in items&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
    &lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alt header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/HelloWorld&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vue (compiled by Vue SFC Playground)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineComponent&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_defineComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;openBlock&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_openBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createBlock&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_createBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;renderList&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_renderList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Fragment&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_Fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createElementBlock&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_createElementBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unref&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_unref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toDisplayString&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_toDisplayString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;normalizeProps&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_normalizeProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createElementVNode&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_createElementVNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createTextVNode&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_createTextVNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;withCtx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;_withCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HelloWorld&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HelloWorld.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Header.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HeaderAlt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/HeaderAlt.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__sfc__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_defineComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;__name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamicAttribute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alt-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;_openBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;_createBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;HelloWorld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vite + Vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_withCtx&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_openBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;_createBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HeaderAlt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_openBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;_createBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})),&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_withCtx&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_openBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
              &lt;span class="nx"&gt;_createElementBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;_Fragment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;_renderList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;_createElementVNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nx"&gt;_normalizeProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_unref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dynamicAttribute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;}),&lt;/span&gt;
                    &lt;span class="nx"&gt;_toDisplayString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="cm"&gt;/* TEXT, FULL_PROPS */&lt;/span&gt;
                  &lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}),&lt;/span&gt;
                &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="cm"&gt;/* STABLE_FRAGMENT */&lt;/span&gt;
              &lt;span class="p"&gt;)),&lt;/span&gt;
              &lt;span class="nx"&gt;_createTextVNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                  &lt;span class="nx"&gt;_toDisplayString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAlternateHeader&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alt header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="cm"&gt;/* TEXT */&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="na"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="cm"&gt;/* STABLE */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;__sfc__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/App.vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;__sfc__&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The translation from JSX to JS is simple because tags map to function calls and props map to object properties.&lt;/p&gt;

&lt;p&gt;Vue has a more custom DSL where more code is needed to translate it to an executable Javascript code. This also comes into play when using it with Typescript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue's Typescript support is... fine
&lt;/h2&gt;

&lt;p&gt;If you are on a &amp;lt; Vue 2.7 project, you will have a bad experience with Typescript. You might even have to use other libraries to rewrite your Vue components using classes.&lt;/p&gt;

&lt;p&gt;Even with a modern Vue 3 project, the Typescript support can be described as "fine". Since Vue templates don’t easily translate to Javascript, it has to do complex compilation to get the types working. Even then, certain things like context (provide/inject) and event arguments need typing manually.&lt;/p&gt;

&lt;p&gt;Do you remember the earlier app example? Here is the internal Typescript representation of it's code using Volar (&lt;a href="https://gist.github.com/NikhilVerma/66b2f986433a5b2ffebf48599780eba2"&gt;gist.github.com&lt;/a&gt;). I had to use a gist because the generated code is huge to allow proper type inference to work.&lt;/p&gt;

&lt;p&gt;This is the outcome of using a custom DSL in Vue, it has it's tradeoffs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Scoped styles out of the box
&lt;/h2&gt;

&lt;p&gt;Vue also provides &lt;a href="https://vuejs.org/api/sfc-css-features.html#scoped-css"&gt;scoped styles&lt;/a&gt; out of the box. This means the following code will not leak CSS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="nx"&gt;scoped&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/style&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;hi&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I still prefer JSX over Vue templates but I vastly prefer the ref/reactive/computed systems of Vue.&lt;/p&gt;

&lt;p&gt;My ideal UI library will offer first class template syntax of JSX and reactivity of Vue. Vue does &lt;a href="https://vuejs.org/guide/extras/render-function.html"&gt;support JSX&lt;/a&gt; but it has it's own set of &lt;a href="https://vuejs.org/guide/extras/render-function.html#vnodes-must-be-unique"&gt;gotchas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Vue was designed with a certain set of tradeoffs, and it does them really well. If you have a Vue 3 codebase you will not find it too difficult to switch from React.&lt;/p&gt;

&lt;p&gt;If you have a Vue 2 codebase however... &lt;a href="https://dev.to/nikhilverma/from-vue-2-to-3-a-long-journey-58ff"&gt;Read my post here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why are you testing?</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Fri, 18 Aug 2023 04:55:32 +0000</pubDate>
      <link>https://dev.to/nikhilverma/why-are-you-testing-1g8n</link>
      <guid>https://dev.to/nikhilverma/why-are-you-testing-1g8n</guid>
      <description>&lt;p&gt;You might already have some quick answers in mind for this question. I totally get it — I do too.&lt;/p&gt;

&lt;p&gt;I'm not here to talk you out of testing or the usual testing processes. I just want to nudge you to think about why you're testing in the first place. Figuring out and questioning the "why" can really steer you towards the right testing methods and tools.&lt;/p&gt;

&lt;p&gt;Here are a few questions you need to ask yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your project
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What's the baseline quality that's expected for a project like yours?&lt;/li&gt;
&lt;li&gt;Is uniform quality necessary across all interactions? For example, consider the user experience of your sign-up process. Is it worthwhile to channel efforts into refining that experience rather than testing a section that only a few users might visit?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your audience
&lt;/h2&gt;

&lt;p&gt;If you are building something which has yet to see the light of the day. Would you rather have it&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launch early but in a rough shape but risk a lukewarm reception from initial users?&lt;/li&gt;
&lt;li&gt;Launch it later in a polished state but risk finding out that no one's interested?&lt;/li&gt;
&lt;li&gt;Which user paths will be the most traveled? Consider interactions like visits to the settings page, are they frequent?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Your team
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How big is your team?&lt;/li&gt;
&lt;li&gt;Is everyone aligned on what the goal is?&lt;/li&gt;
&lt;li&gt;What skill set and experience does your team possess?&lt;/li&gt;
&lt;li&gt;What budget are you operating under? How long can you afford to develop before you need to generate revenue.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Considering the context that you are in will help you truly answer the question of "Why are you testing?".&lt;/p&gt;

&lt;p&gt;Testing is not a one-size-fits-all approach. It requires thoughtful consideration of your audience, team, and project context. &lt;/p&gt;

&lt;p&gt;By asking these questions and reflecting on your unique circumstances, you can shape your testing strategy to deliver a project that truly meets the needs of your users.&lt;/p&gt;

</description>
      <category>qa</category>
      <category>testing</category>
      <category>automation</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>From Vue 2 to 3: A long journey</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Thu, 17 Aug 2023 14:19:49 +0000</pubDate>
      <link>https://dev.to/nikhilverma/from-vue-2-to-3-a-long-journey-58ff</link>
      <guid>https://dev.to/nikhilverma/from-vue-2-to-3-a-long-journey-58ff</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This is a long post detailing my journey of migrating a project from Vue 2 to 3. It contains the setbacks and the victories I faced and providing breakdown of the issues as well.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Learning Vue
&lt;/h2&gt;

&lt;p&gt;I joined my current team on May 2021, the project I was working with used Vue 2.6 And Vuex 3.4 and plain Javascript.&lt;/p&gt;

&lt;p&gt;Vue 3.0 was already out at that point yet it was not the "default" Vue version. It won't be the case until &lt;a href="https://blog.vuejs.org/posts/vue-3-as-the-new-default" rel="noopener noreferrer"&gt;Feb 7, 2022&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The need for types
&lt;/h2&gt;

&lt;p&gt;I was new to Vue back then and came from a React and Typescript codebase.&lt;/p&gt;

&lt;p&gt;My first commit was to add a &lt;code&gt;tsconfig.json&lt;/code&gt; just to have some intellisense. The pain point at that time was that &lt;code&gt;Vue.extend&lt;/code&gt; for Vue 2 had poor support for Typescript. I wanted to fix it.&lt;/p&gt;

&lt;p&gt;The solution came with the usage of &lt;a href="https://github.com/vuejs/vue-class-component" rel="noopener noreferrer"&gt;vue-class-component&lt;/a&gt; with a combination of &lt;a href="https://github.com/kaorun343/vue-property-decorator" rel="noopener noreferrer"&gt;vue-property-decorator&lt;/a&gt;. Using a combination of the two allowed us to define components in a way that provide a reasonable amount of type safety.&lt;/p&gt;

&lt;p&gt;The Vue component code would look something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Prop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-property-decorator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YourComponent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Vue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;propA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;propB&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;propC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The migration to class components was done incrementally, any new features or components written using the class syntax. Older components were refactored out whenever we needed to change them.&lt;/p&gt;

&lt;p&gt;We also switched our VSCode plugin from Vetur to &lt;a href="https://marketplace.visualstudio.com/items?itemName=vue.volar" rel="noopener noreferrer"&gt;Volar&lt;/a&gt; which had better Typescript support.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Using Vuex wrong
&lt;/h2&gt;

&lt;p&gt;The Vuex stores were in the "traditional" redux style. They had a file structure like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;store/
    actions.js
    getters.js
    index.js
    mutations.js
    state.js
    types.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good luck finding the right store by name! Or landing on the right action/mutation because there was no support for intellisense.&lt;/p&gt;

&lt;p&gt;This got solved by migrating the stores to use &lt;a href="https://www.npmjs.com/package/vuex-module-decorators" rel="noopener noreferrer"&gt;vuex-module-decorators&lt;/a&gt; which came with a much better support for Typescript. Plus you get all actions/mutations/getters etc in a single file so it's easier to understand. This was also done incrementally.&lt;/p&gt;

&lt;p&gt;Here is how the syntax for the decorator stores will look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VuexModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vuex-module-decorators&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;VuexModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Mutation&lt;/span&gt;
  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. First setback
&lt;/h2&gt;

&lt;p&gt;There was a spike done into the feasibility of migrating to Vue 3. It didn't last long because of the following reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We used an internal UI library which depended on components that weren't compatible or were unstable with Vue 3 at the time

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://vee-validate.logaretm.com/v3/" rel="noopener noreferrer"&gt;vee-validate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/v-tooltip" rel="noopener noreferrer"&gt;v-tooltip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vue-showdown.js.org/" rel="noopener noreferrer"&gt;vue-showdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vue-multiselect.js.org/" rel="noopener noreferrer"&gt;vue-multiselect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jbaysolutions.github.io/vue-grid-layout/" rel="noopener noreferrer"&gt;vue-grid-layout&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;We couldn't figure out a reasonable strategy to migrate the UI library to Vue 3. Because then we would need to manage two versions of the same library.&lt;/li&gt;

&lt;li&gt;We did not want to stop shipping our product while the migration was happening, it either had to be incremental or quick.&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;vee-validate&lt;/code&gt; and the &lt;code&gt;$slots&lt;/code&gt; problem
&lt;/h2&gt;

&lt;p&gt;The biggest hurdle that we faced was &lt;code&gt;vee-validate&lt;/code&gt;. It is a form validation library built for Vue. The pain was that the library API for Vue 2 and 3 were &lt;strong&gt;entirely&lt;/strong&gt; different.&lt;/p&gt;

&lt;p&gt;You would almost call it another library, this was due to the new breaking &lt;code&gt;$slots&lt;/code&gt; implementation in Vue 3 (more on that later).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps1lbe6n086plryn1f1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fps1lbe6n086plryn1f1g.png" alt="I thought about that initially, but aside from how hard is it to find a new name, I think it is better to build on the existing popularity of vee-validate especially not everyone who used vee-validate will switch to the new package and I would be gettings tons of requests for Vue 3 support."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our UI library had used vee-validate to create a "form builder" that was used over &lt;strong&gt;30&lt;/strong&gt; times in our application. We couldn't find an incremental upgrade path here. Either we migrate them all or we migrate none.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. $slots between Vue 2 and Vue 3
&lt;/h3&gt;

&lt;p&gt;The official migration documentation for &lt;a href="https://v3-migration.vuejs.org/breaking-changes/slots-unification.html" rel="noopener noreferrer"&gt;slots unification&lt;/a&gt; goes over the API differences. It omits a major point: In Vue 3 it's not possible to get the DOM elements of the slots directly from the reference to the slot.&lt;/p&gt;

&lt;p&gt;You would need to render your parent, then use the parent's HTML node to get the child slot selector. Or use some kind of inject/provide workaround.&lt;/p&gt;

&lt;p&gt;This is the main reason why the &lt;code&gt;vee-validate&lt;/code&gt; library could not use it's earlier, simpler API.&lt;/p&gt;

&lt;p&gt;Here is a link to the &lt;a href="https://github.com/logaretm/vee-validate/blob/v3/src/components/Provider.ts#L215" rel="noopener noreferrer"&gt;problematic part&lt;/a&gt; of the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnw2i9bfgvugbzs0akyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnw2i9bfgvugbzs0akyq.png" alt="Code example of where slots don't work"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This wouldn't work anymore in Vue 3 because you can't directly extract the DOM nodes of children from the &lt;code&gt;$slots&lt;/code&gt; property.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A new hope
&lt;/h2&gt;

&lt;p&gt;On 1st July 2022 &lt;a href="https://github.com/vuejs/vue/blob/main/CHANGELOG.md#270-2022-07-01" rel="noopener noreferrer"&gt;Vue 2.7&lt;/a&gt; was released. It finally ported many features from Vue 3 back to Vue 2. This was a pivotal moment in our development because we it came with a solid Typescript support out of the box using &lt;code&gt;defineComponent&lt;/code&gt;. This was better than vue-class-component because it &lt;a href="https://github.com/vuejs/vue-class-component/pull/614" rel="noopener noreferrer"&gt;needed some hacks&lt;/a&gt; to get inter-component type inference to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/matrunchyk" rel="noopener noreferrer"&gt;@matrunchyk&lt;/a&gt; had done a fantastic job &lt;a href="https://github.com/vuejs/vue-codemod/pull/32" rel="noopener noreferrer"&gt;creating a codemod&lt;/a&gt; to automatically migrate Vue class components to the earlier syntax. It was not perfect but workable.&lt;/p&gt;

&lt;p&gt;I made some further modifications to it to get it to work on our project. It's available here if you want to try it for yourself: &lt;a href="https://github.com/NikhilVerma/vue-codemod/blob/main/transformations/vue2-class-component-to-native-typescript.ts" rel="noopener noreferrer"&gt;NikhilVerma/vue-codemod&lt;/a&gt;. It's still not polished enough to consider an upstream pull request but you can hack around to migrate your project with it.&lt;/p&gt;

&lt;p&gt;We finally migrated to Vue 2.7 and &lt;code&gt;defineComponent&lt;/code&gt; using the codemod in a very short time. Also used this to migrate most of the codebase to Typescript. Here is the timeline recorded in our JIRA ticket.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;JS Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5th December 2021&lt;/td&gt;
&lt;td&gt;244&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22nd Feb 2022&lt;/td&gt;
&lt;td&gt;173&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23nd Feb 2022&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24th Feb 2022&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10th Mar 2022&lt;/td&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 Apr 2022&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26 Apr 2022&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 May 2023&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17 Aug 2023&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the same time, we noticed several library dependencies migrated to support Vue 3 with minimal API changes. This was exciting!&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The solution to vee-validate
&lt;/h2&gt;

&lt;p&gt;Now the only thing preventing a smooth migration was the &lt;code&gt;vee-validate&lt;/code&gt; library. We were in luck because our UI team was working on a second version of the &lt;a href="https://flow.cldcvr.com/" rel="noopener noreferrer"&gt;Flow UI&lt;/a&gt; library which used &lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Lit&lt;/a&gt;. It also offered a replacement Form builder which could take over the vee-validate one.&lt;/p&gt;

&lt;p&gt;Since the new library used Lit, it was agnostic of Vue 2 or 3 so incremental migration was easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Let's do this!
&lt;/h2&gt;

&lt;p&gt;The final plan was in place:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Migrate all old form and vee-validate instances with the new Lit ones&lt;/li&gt;
&lt;li&gt;Migrate remaining UI components in the library to support Vue 3&lt;/li&gt;
&lt;li&gt;Migrate the application to Vue 3 in a single PR&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first step took around a month to complete, as the instances has to be migrated incrementally. The second step was faster, around a week to migrate because most of the other libraries had good backwards compatibility.&lt;/p&gt;

&lt;p&gt;The third was easy too. Vue codemod helped a lot, and ESLint + Typescript caught most issues. The lifesaver was our automation suite which made sure we didn't break any critical functionality in the application.&lt;/p&gt;

&lt;p&gt;Here is a screenshot of the pull request which migrated our app to Vue 3. (edited out the internal bits)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7axiuv9j2cmxkh64uv3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7axiuv9j2cmxkh64uv3.png" alt="PR overview of the Vue migration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After almost two years. We migrated to Vue 3. Because we prepared so well the final merge was uneventful. We had a few bugs discovered afterwards, mostly due to how &lt;a href="https://v3-migration.vuejs.org/breaking-changes/attribute-coercion.html" rel="noopener noreferrer"&gt;boolean attributes have changed&lt;/a&gt; in Vue 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Aftermath
&lt;/h2&gt;

&lt;p&gt;We are happily using Vue 3 since then. And I hope that future Vue upgrades will be less eventful and not need writing posts like these.&lt;/p&gt;

&lt;p&gt;I hope this was helpful to you, I am sure that there are others who are still using Vue 2 on larger codebase and having a tough time with it. And I hope by sharing their experiences it will encourage others to start their migration journey.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>XPaths in the modern age</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Thu, 01 Jun 2023 13:07:53 +0000</pubDate>
      <link>https://dev.to/nikhilverma/xpaths-in-the-modern-age-1gah</link>
      <guid>https://dev.to/nikhilverma/xpaths-in-the-modern-age-1gah</guid>
      <description>&lt;p&gt;XPaths are selectors you can use to query the elements in your document (HTML, XML etc). They are similar but complex and more featureful versions of CSS query selectors many are used to.&lt;/p&gt;

&lt;p&gt;This is an example XPath which can find the Google Search button by starting from the search input. It's a roundabout way of doing this but it shows the powers of XPath over normal query selectors which can't do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`//textarea/ancestor::form//input[@value="Google Search"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  XPath with Web components
&lt;/h2&gt;

&lt;p&gt;Since XPath is a pretty old specification (&lt;a href="https://www.w3.org/TR/1999/REC-xpath-19991116/"&gt;created in 1999&lt;/a&gt; and last updated in 2016). It doesn't specify how to operate with Web components and shadow DOM.&lt;/p&gt;

&lt;p&gt;So if you want to write an XPath which selects an element inside the shadow DOM of a component you simply can't. The only solution is to find the component with the XPath. Then use the &lt;code&gt;shadowRoot&lt;/code&gt; node to then further drill down into the component. For nested components it quickly becomes impractical.&lt;/p&gt;

&lt;p&gt;To be fair this problem with shadow DOM is also present in querySelectors which don't work with it either. Lit has it's &lt;a href="https://lit.dev/docs/components/shadow-dom/"&gt;own section&lt;/a&gt; dedicated to using custom &lt;code&gt;@query&lt;/code&gt; decorators to find elements.&lt;/p&gt;




&lt;h2&gt;
  
  
  XPath with Vue 3
&lt;/h2&gt;

&lt;p&gt;There is a very specific and peculiar problem that you face when using XPaths with Vue 3, which took me a long time to debug and find out.&lt;/p&gt;

&lt;p&gt;Vue 3 when rendering child slots appends what's called "anchor" nodes to help it optimise it's updates. For example if you have a component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
   Hello World!
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you render it in Vue 3 like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What Vue 3 will do is output the following DOM structure&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    ""
    "Hello World!"
    ""
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;""&lt;/code&gt; is an empty text node. This trips up the &lt;code&gt;contains(text())&lt;/code&gt; API of XPath. So if you were relying on your elements containing a specific text you won't be able to do that anymore.&lt;/p&gt;

&lt;p&gt;Here is a Github issue with examples. &lt;a href="https://github.com/vuejs/core/issues/8444"&gt;vue/core/issues/8444&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A workaround
&lt;/h2&gt;

&lt;p&gt;To solve both of the problems above, I had to ponyfill XPath in our application.&lt;/p&gt;

&lt;p&gt;There was an excellent &lt;code&gt;xpath&lt;/code&gt; polyfill library which I forked and modified to add support for both Shadow DOM and Vue 3 text nodes.&lt;/p&gt;

&lt;p&gt;This makes it "non spec" but it solves our needs and doesn't impact our development velocity.&lt;/p&gt;

&lt;p&gt;It's published here &lt;a href="https://github.com/NikhilVerma/xpath-next"&gt;xpath-next&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;//span&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contextNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contextNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isHtml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;My key takeaway from this experience is reliable technologies stop being so because the ecosystem moves on and makes them incompatible.&lt;/p&gt;

&lt;p&gt;One could argue that Vue 3 behaviour should be fixed. But the fact that Web components don't work transparently with XPaths makes it hard to justify using it in it's original specification.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>xpath</category>
      <category>webcomponents</category>
      <category>lit</category>
    </item>
  </channel>
</rss>
