<?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.us-east-2.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>How I'd do engineering for the next year</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:27:22 +0000</pubDate>
      <link>https://dev.to/nikhilverma/how-id-do-engineering-for-the-next-year-5d1a</link>
      <guid>https://dev.to/nikhilverma/how-id-do-engineering-for-the-next-year-5d1a</guid>
      <description>&lt;p&gt;Until recently, an AI agent would do a few things, then stop and ask me questions, or ask me for the next steps. Somewhere in the last six months that changed: now it just runs, until it genuinely cannot go any further.&lt;/p&gt;

&lt;p&gt;There was no lightning-bolt moment for me. It was a collection of moments, a slow creep. You're working along, and one day you catch yourself thinking: hang on... the task that took me some time a couple of months back was now completed almost autonomously by this AI agent. And then it happens again a few weeks later, with something bigger.&lt;/p&gt;

&lt;p&gt;Here's what that creep did to my work: in roughly the last year, I've written fewer than a hundred lines of code by hand. I don't really write code anymore; I review it. And lately I don't even review every line; I sample it.&lt;/p&gt;

&lt;p&gt;Since you probably don't know me: I've spent almost twenty years building web applications and user-facing products, several of those managing teams of ten-plus people, including products with millions of users at Bumble.&lt;/p&gt;

&lt;p&gt;This post is about what changed between December 2025 and July 2026, where the trend is going, and how I'm changing the way I do engineering to get ahead of it. Compressed into one thought: the rules have changed, and people who think clearly and write clearly are going to matter more than ever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three eras in three years
&lt;/h2&gt;

&lt;p&gt;First, AI was a good autocomplete and not much else. Then came about a year of closely-directed work, the Copilot era: genuinely useful, but you had to direct it tightly: look at these three files, change this function to behave like this, put this UI component here, follow that reference file. If you did that, it did a pretty good job. Honestly, it felt like things wouldn't change much from there.&lt;/p&gt;

&lt;p&gt;Then, over a few weeks in late 2025, the frontier labs shipped a wave of models&lt;sup id="fnref1"&gt;1&lt;/sup&gt;, and agents stopped needing step-by-step instructions. They started taking high-level &lt;em&gt;intent&lt;/em&gt; and carrying it over hours of work.&lt;/p&gt;

&lt;p&gt;Why? My hypothesis (and it is only a hypothesis) is that while humans were closely directing AI through the Copilot era, the AI companies were learning from that interaction. We were acting as quasi-agents, and the agents learned from us. The standard account from people who train these systems is different: reinforcement learning on verifiable rewards, meaning models trained against outcomes like "the tests pass" in sandboxed environments.&lt;sup id="fnref2"&gt;2&lt;/sup&gt; The two stories aren't mutually exclusive, and either way the result is the same: the agents stopped needing me between every step.&lt;/p&gt;

&lt;h2&gt;
  
  
  My job is a factory now
&lt;/h2&gt;

&lt;p&gt;My work has turned into a car factory. If you hand-build a car, you inspect every piece, every gear. If you have smart workers on an assembly line, you direct instead of build, but you still check everything that comes off the line. The stage after that is where I live now: one agent makes, another agent checks, and my job is directing. I'm an orchestrator, there are minions doing the work, and I'm sampling their output and providing the taste.&lt;/p&gt;

&lt;p&gt;Diligence doesn't disappear, it becomes random sampling: random lines, random areas, enough distribution to catch what usually goes wrong. The move that makes it compound: everything sampling catches gets converted into static enforcement (a lint rule, a code-review instruction, a pipeline check) so the same mistake can't happen again. Google's recent whitepaper lands on the same rule: "add a rule every time the agent does something it should not do again."&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Probably the most important thing I've learned this year: the loop you run depends entirely on what you're building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different context, different judgments
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0wb3okw3psi81plg2t5b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0wb3okw3psi81plg2t5b.png" alt="Flowchart: a new task arrives, gets sized by criticality and t-shirt size, then splits: the experimental path loops prompt → agent builds → sample the output without ever opening the code; the real-product path runs rant → plan → milestones, vertical slices, a draft PR reviewed by a human at a depth scaled to stakes. Every path ends at a mandatory human taste test." width="800" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before any work starts, a few knobs get set: how critical is this to the business (an experimental feature, by default, cannot be critical), how big is it, how many decisions are hiding inside it. Days-based estimation doesn't work anymore, because things happen in hours, but t-shirt sizing actually works now. You get a rough size and pick a path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The throwaway path.&lt;/strong&gt; For quick internal tools and interim artifacts, I've written zero lines and &lt;em&gt;reviewed&lt;/em&gt; zero lines. I jump literally from my prompt to the output: prompt, agent works, I sample the output, go again. The code is never opened. I know how reckless that sounds; I'll come back to why I don't think it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real-project path.&lt;/strong&gt; For the codebase I actually live in, I'm more involved, because I don't want the models drifting in the wrong direction. The loop becomes: I do the rant, I do the plan, I break the plan into reviewable milestones. (The rant is literal: my typing never kept up with my thinking, so prompts start as speech through a dictation app called Handy.&lt;sup id="fnref4"&gt;4&lt;/sup&gt;)&lt;/p&gt;

&lt;p&gt;The milestone loop is honestly a bit funny: the agent writes the code and opens a draft pull request against me, using my own account; I go in as a human, review my own pull request, leave comments in GitHub, and tell the agent I've left comments for it. Big features get built as vertical slices that fit together like Tetris, each slice testable on its own, the whole thing done when the slices line up, not as a waterfall where everything collapses at the end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The floor under every path.&lt;/strong&gt; Whatever the AI builds, a human actually has to &lt;em&gt;use&lt;/em&gt; the feature. You are the taster. Someone made food; you taste it, you check whether it's tastefully done, whether it makes sense. This part is mandatory.&lt;/p&gt;

&lt;p&gt;Between those extremes, review depth scales with stakes. Business-critical code, I read all of it. Non-critical, I read some. Isolated one-offs, sometimes none at all. Different context, different judgments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nobody reads every line
&lt;/h2&gt;

&lt;p&gt;This is where I part ways with the official advice. Google's whitepaper, which agrees with almost everything else in this post, says plainly: review every line the agent produces that is going to ship.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;I don't think that's realistic, for two reasons.&lt;/p&gt;

&lt;p&gt;The first: AI is really, really good at pattern recognition. Distill the patterns a reviewer should look out for, and one AI can impartially catch issues while another fixes them, in a recursive loop, combined with tooling we've had for years: static analysis, linters, type checkers, test runners, smoke tests, property-based testing, fuzzing. The agents can help you &lt;em&gt;write&lt;/em&gt; those tools too. With good enough guardrails, you don't have to stress about reading every single line. It's a combination of tools, not one person sitting on a laptop with nothing else in place.&lt;/p&gt;

&lt;p&gt;The second is more uncomfortable: realistically, no human being honestly reads every line of code they review. I know this is controversial, and most people will say they do. But almost every engineer I've worked with has admitted, at some point, that they didn't read everything. They sampled it, checked the conventions, looked hard at the critical parts they knew about, and approved. We've been sampling all along, just without the guardrails we have now. And it's not like every line of code is headed for a nuclear or medical facility. Applying the right depth of review in the right context has always been the actual job.&lt;/p&gt;

&lt;p&gt;The tier of code where you read everything is real, but it's shrinking, and guardrails are what shrink it.&lt;/p&gt;

&lt;p&gt;I've seen this play out at scale twice this year. In public: Bun rewrote roughly a million lines of Zig into Rust in eleven days, using 64 parallel agent instances in an implementer/reviewer pattern, with its TypeScript test suite as the conformance harness.&lt;sup id="fnref5"&gt;5&lt;/sup&gt; It shipped with 19 known regressions (since fixed) and drew fair criticism for its heavy use of &lt;code&gt;unsafe&lt;/code&gt;. I'm including those deliberately, because they make the point: the test suite caught what mattered.&lt;/p&gt;

&lt;p&gt;On my own team: we migrated our entire Vue component library from &lt;code&gt;.vue&lt;/code&gt; single-file components to TypeScript TSX (still Vue as the framework) using AI plus static migration tools I built. The static tools converted what they could mechanically, the agents took care of the rest. With good test coverage going in, I'd estimate 95% of the work landed without a single bug; what bit us were migration artifacts (global Vue and Nuxt configuration changes) and some minor class-ordering issues. (I've written about the TSX setup &lt;a href="https://dev.to/nikhilverma/how-i-am-using-vue-with-typescript-7-today-3ge3"&gt;in a previous post&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;The counter-evidence: a widely-cited METR study found experienced developers using AI took 19% &lt;em&gt;longer&lt;/em&gt; on certain tasks, mostly verifying and correcting AI output.&lt;sup id="fnref6"&gt;6&lt;/sup&gt; I can't argue with their data, but it doesn't match anything I've seen, and I think the difference is the guardrails. What I've seen is prototyping going from weeks to hours, almost a J curve. At Bumble, on a highly experimental platform, these tools would have let us ship ten times the experiments. If verification is where the time goes, verification is what you should industrialize, which is the rest of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The curve is accelerating
&lt;/h2&gt;

&lt;p&gt;Everything above already happened. The reason I'm confident it keeps going is the trend line: METR tracks the length of task, measured in human-professional time, that models complete autonomously, and that horizon has been doubling roughly every seven months since 2019, &lt;em&gt;accelerating&lt;/em&gt; since 2023.&lt;sup id="fnref7"&gt;7&lt;/sup&gt; The Bun rewrite is one point on that curve. Extrapolate twelve months, and the question stops being whether the agent can do the task, and becomes whether your engineering team is set up to direct and verify that much output.&lt;/p&gt;

&lt;p&gt;If I were running a ten-person team, here's what I'd change on Monday morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change Monday morning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Everyone runs agents in parallel.&lt;/strong&gt; You can direct multiple tasks at once now; one agent per person leaves most of the value on the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agents move off laptops.&lt;/strong&gt; When your laptop closes, your agents die; on a cloud environment they keep running. We attach with tmux, and recently I've been trying out Herdr.&lt;sup id="fnref8"&gt;8&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboard agents like employees, but respect the context.&lt;/strong&gt; An agent with no guidance does whatever it wants, but stuffing its context with your documentation fails just as drastically. Context is precious; don't pollute it. What works: self-documented code that records the &lt;em&gt;why&lt;/em&gt;, and a knowledgebase the agent can grep when it decides it needs more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Force the why.&lt;/strong&gt; Agents love to narrate &lt;em&gt;what&lt;/em&gt; they're doing, but the code already tells you that. Force them to document &lt;em&gt;why&lt;/em&gt;: the thing you need three months later, staring at a line and wondering why it was written in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate on evals, not demos.&lt;/strong&gt; Almost everybody should have an internal eval for what they're building; a passing demo tells you very little about how the thing behaves the rest of the time.&lt;sup id="fnref9"&gt;9&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review from altitude.&lt;/strong&gt; AI reviewers take the boring layer (style violations, easily-exploitable security gaps) and your review moves up to architecture, reusability, data structures, the bigger patterns. You're allowed to zoom out. A developer becomes an architect now, and that's a huge thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On hiring: I don't have an answer.&lt;/strong&gt; Hiring has become difficult, because every portfolio project is now vibe-coded, so you have no idea what you're looking at. You have to somehow evaluate taste: how much care and attention went into the work. We've struggled with it, and I won't pretend otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discipline: fighting brain rot
&lt;/h2&gt;

&lt;p&gt;I'd be lying if I ended on the prescriptions, because the hardest problem on that Monday morning is me.&lt;/p&gt;

&lt;p&gt;These models make it so easy to not read anything and just ship. You have to actively fight yourself, and I struggle with this personally. The temptation: prompt something, barely test the output, say "yep, ship it", because the models are good enough that whatever they produce will &lt;em&gt;mostly&lt;/em&gt; work, and the saved time feels like it buys the next feature, the next experiment, the next side project.&lt;/p&gt;

&lt;p&gt;But it leads to complete brain rot, for both the agent and for you. You lose all sense of what's actually being built: an old man yelling at the cloud, making claims about your own system without verifying that any of them back up. Google's paper puts it well: AI "multiplies both your strengths and your weaknesses."&lt;sup id="fnref10"&gt;10&lt;/sup&gt; The taste test, a human actually using the thing, is what keeps that multiplier pointed the right way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code as a compiled artifact
&lt;/h2&gt;

&lt;p&gt;Everything above is my plan for the next year. Here's where I think it goes a couple of years out.&lt;/p&gt;

&lt;p&gt;I don't write binary. I don't write assembly. I don't even write C. Node and Chrome are built on C, which becomes assembly, which becomes something I've never once needed to look at, and I just write JavaScript on top. With the next generation of agents, I think the whole codebase becomes almost like a compiled artifact. Just as I don't read assembly today, you won't need to read the code agents produce. It will be some internal representation in their latent space.&lt;/p&gt;

&lt;p&gt;Someone will say: but you're becoming dumb. Which is true, but I'm also dumb now. I don't know assembly, and somehow I still write TypeScript and do useful work.&lt;/p&gt;

&lt;p&gt;It's not a one-to-one mapping, of course. But when your code gets compiled, most of the time (and I want to stress &lt;em&gt;most of the time&lt;/em&gt;) you don't care what bytes it compiled to, because it works and everything is fine. Similarly, give an agent a detailed specification and guardrails, and most of the time the code it produces just works, as expected. Neither case means you go blind; it's always context. If your application is high-performance and the compiled code isn't performing, you start investigating. If your prompted code isn't good, you do the same thing: you dig down into it.&lt;/p&gt;

&lt;p&gt;So the rules change a lot. Your ability to think clearly, to write clearly, to communicate clearly becomes the important thing, because good thinkers and good writers can impart their taste, their wisdom, and their vision, and the agents do the work.&lt;/p&gt;

&lt;p&gt;That's the bet I'm making for the next year. I'm writing it down so that next July I can come back and check whether any of it backed up.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Gemini 3 Pro (Nov 18, 2025), GPT-5.1 Codex-Max (Nov 19), Claude Opus 4.5 (Nov 24), and GPT-5.2 (December). Simon Willison's &lt;a href="https://simonwillison.net/2025/Dec/31/the-year-in-llms/" rel="noopener noreferrer"&gt;2025: The year in LLMs&lt;/a&gt; is the best chronicle of the wave.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Nathan Lambert's writing at &lt;a href="https://www.interconnects.ai/" rel="noopener noreferrer"&gt;Interconnects&lt;/a&gt; is the clearest explanation of reinforcement learning with verifiable rewards, a term his team coined.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Google, &lt;a href="https://www.kaggle.com/whitepaper-the-new-SDLC-with-vibe-coding" rel="noopener noreferrer"&gt;The New SDLC With Vibe Coding&lt;/a&gt; (May 2026), by Addy Osmani, Shubham Saboo and Sokratis Kartakis; there's an &lt;a href="https://addyosmani.com/blog/new-sdlc-vibe-coding/" rel="noopener noreferrer"&gt;author's summary on Osmani's blog&lt;/a&gt;. A genuinely useful paper, with the caveat that its later chapters lean promotional about Google's own tooling.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://github.com/cjpais/Handy" rel="noopener noreferrer"&gt;Handy&lt;/a&gt;: free, open source, fully offline speech-to-text. Plenty of similar apps exist, with new ones every month; I just haven't had a reason to switch.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Simon Willison, &lt;a href="https://simonwillison.net/2026/Jul/8/rewriting-bun-in-rust/" rel="noopener noreferrer"&gt;Rewriting Bun in Rust&lt;/a&gt;, and The Register's &lt;a href="https://www.theregister.com/devops/2026/05/14/anthropics-bun-rust-rewrite-merged-at-speed-of-ai/" rel="noopener noreferrer"&gt;coverage of the merge&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;METR, &lt;a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/" rel="noopener noreferrer"&gt;Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity&lt;/a&gt; (&lt;a href="https://arxiv.org/abs/2507.09089" rel="noopener noreferrer"&gt;paper&lt;/a&gt;). Worth noting the study used early-2025 tools (Cursor with Claude 3.5/3.7 Sonnet), from before the autumn wave this post is about.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;METR, &lt;a href="https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks/" rel="noopener noreferrer"&gt;Measuring AI Ability to Complete Long Tasks&lt;/a&gt;: the ~7-month doubling time, revised faster in their Time Horizon 1.1 update (January 2026).&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;&lt;a href="https://herdr.dev/" rel="noopener noreferrer"&gt;Herdr&lt;/a&gt;: an agent-aware terminal multiplexer: tmux that understands agents, showing at a glance which are blocked, working, or done.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;"Set the bar at the eval, not the demo." (The New SDLC With Vibe Coding, p. 44.)&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn10"&gt;
&lt;p&gt;"AI is a force multiplier — and it multiplies both your strengths and your weaknesses." (The New SDLC With Vibe Coding, p. 47.)&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>software</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I am using Vue with Typescript 7 today</title>
      <dc:creator>Nikhil Verma</dc:creator>
      <pubDate>Fri, 10 Jul 2026 10:48:02 +0000</pubDate>
      <link>https://dev.to/nikhilverma/how-i-am-using-vue-with-typescript-7-today-3ge3</link>
      <guid>https://dev.to/nikhilverma/how-i-am-using-vue-with-typescript-7-today-3ge3</guid>
      <description>&lt;p&gt;TypeScript 7 shipped this week. The Go-native compiler, the one with the order-of-magnitude speed claims, is now just &lt;code&gt;typescript@7&lt;/code&gt; on npm. And buried in the announcement is a caveat that reads like bad news for Vue developers: tools that embed TypeScript into their own language services — Volar among them — can't use it yet, so "workflows that use Vue, MDX, Astro, Svelte and others will likely not yet be able to leverage TypeScript 7."&lt;/p&gt;

&lt;p&gt;Our Vue app has been on the native compiler the whole time. Every component, type-checked by TypeScript 7's toolchain, on GA week, no plugins, no waiting for anything to be ported.&lt;/p&gt;

&lt;p&gt;We write Vue in &lt;code&gt;.tsx&lt;/code&gt; files. The reason that works points at a misconception about what Vue actually &lt;em&gt;is&lt;/em&gt;, and the payoff goes well beyond one fast compiler.&lt;/p&gt;

&lt;h2&gt;
  
  
  The .vue file was always optional
&lt;/h2&gt;

&lt;p&gt;Say "Vue" and most people picture a &lt;code&gt;.vue&lt;/code&gt; file: a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; block, a &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; block, maybe some scoped styles. The single-file component is Vue's signature authoring format, it's what every tutorial teaches, and it's genuinely pleasant. Somewhere along the way, the public assumption calcified into &lt;em&gt;Vue requires .vue files&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't, and Vue has never claimed it does. The docs describe render functions and JSX as a fully supported, first-class way to write components. Vue ships a JSX transform, a &lt;code&gt;jsxImportSource&lt;/code&gt;, and complete JSX type definitions out of the box. The SFC is sugar over a component model that is plain JavaScript: an object with a &lt;code&gt;setup&lt;/code&gt; function that wires up reactive state and returns a function that renders it.&lt;/p&gt;

&lt;p&gt;Which means a Vue component can be an ordinary TypeScript file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="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="k"&gt;default&lt;/span&gt; &lt;span class="nf"&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;UsageBadge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&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="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="nf"&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;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bump&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="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"badge"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;bump&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything here is documented Vue. &lt;code&gt;setup&lt;/code&gt; runs once per component instance and closes over the reactive state; the arrow function it returns is the render function, and Vue re-runs &lt;em&gt;that&lt;/em&gt; — and only that — when something it read changes. This shape is more honest about Vue's execution model than the SFC is: the one-time setup and the re-running render are two visibly different functions, rather than a compiler convention you have to know about.&lt;/p&gt;

&lt;p&gt;The configuration to make TypeScript understand it is two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&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;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"preserve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsxImportSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vue"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's no preprocessor and no custom language service to install. A &lt;code&gt;.tsx&lt;/code&gt; file with Vue as its JSX source is just TypeScript, and every tool that speaks TypeScript — the compiler, the linter, your editor, and as of this week a very fast Go binary — handles it natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  The heroics you no longer need
&lt;/h2&gt;

&lt;p&gt;To appreciate what "just TypeScript" buys you, look at what it takes to type-check the file format everyone assumes is mandatory.&lt;/p&gt;

&lt;p&gt;TypeScript has no idea what a &lt;code&gt;.vue&lt;/code&gt; file is. So Volar — the language tooling behind Vue — performs a genuinely impressive maneuver: it intercepts TypeScript's view of the filesystem, and wherever the compiler would read a &lt;code&gt;.vue&lt;/code&gt; file, Volar hands it generated TypeScript instead. Your template becomes synthesized code full of type-level gymnastics; source maps stitch the compiler's errors back onto your template expressions. On the editor side you need the Vue extension to make VS Code play along, and for a while in 2024 you needed to understand its "hybrid mode" — which half of the language service answered which kind of request — just to work out why autocomplete had stopped showing suggestions.&lt;/p&gt;

&lt;p&gt;This is admirable engineering. Volar is one of the most technically interesting projects in the frontend ecosystem, and for SFC codebases it works well. But it is a &lt;em&gt;shim layer&lt;/em&gt;, and shim layers have a cost that shows up on the margins: a squiggle that lands a line off because a source map didn't quite align, a rename refactor that doesn't reach into templates, a new TypeScript release that the shim has to chase. This week is the sharpest example yet — TypeScript 7 doesn't expose the API surface the shim hooks into, so the entire &lt;code&gt;.vue&lt;/code&gt; world waits for TypeScript 7.1 and a Volar port before it sees any of the new compiler's speed.&lt;/p&gt;

&lt;p&gt;None of this is a criticism of Vue. It's the structural price of &lt;em&gt;any&lt;/em&gt; custom file format, and Svelte, Astro and MDX are all in the same waiting room. Vue is the one framework in that list that doesn't make you pay it. The escape hatch is documented, and — we can now report from production — entirely boring to live with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reactivity system comes with you
&lt;/h2&gt;

&lt;p&gt;The reflex objection: isn't this just React with extra steps? If I'm writing JSX anyway, what's left of Vue?&lt;/p&gt;

&lt;p&gt;The answer is everything that matters, because Vue's soul is the reactivity system. The difference is easiest to see in the code you &lt;em&gt;don't&lt;/em&gt; write.&lt;/p&gt;

&lt;p&gt;In React, the runtime re-runs your whole component function on every update and cannot know what your effects depend on, so you tell it, by hand, forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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;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;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&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="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// you maintain this list. every hook. every edit.&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Cart: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;total&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every React developer knows the failure modes: a stale closure because a dependency was missed, a render loop because an object identity changed, an exhaustive-deps lint rule arguing with you about what you meant. It's manual memory management for dataflow.&lt;/p&gt;

&lt;p&gt;Vue tracks dependencies &lt;em&gt;at runtime, automatically&lt;/em&gt;. A &lt;code&gt;computed&lt;/code&gt; or a &lt;code&gt;watchEffect&lt;/code&gt; records exactly which reactive values it read while running; when one of those changes, it re-runs. There is no list to maintain because the system derives the list from the truth of what executed:&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;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;price&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="na"&gt;qty&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="c1"&gt;// deeply reactive by default&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;items&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="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qty&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="nf"&gt;watchEffect&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Cart: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;total&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="s2"&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;No dependency arrays, no &lt;code&gt;useCallback&lt;/code&gt; rituals. And because &lt;code&gt;setup&lt;/code&gt; runs once, you don't re-create closures on every render or reach for &lt;code&gt;useRef&lt;/code&gt; to smuggle values past the re-render boundary — state just lives in scope, and the render closure reads it.&lt;/p&gt;

&lt;p&gt;Writing this in TSX changes none of it. The reactivity and the automatic tracking come from the runtime you import from &lt;code&gt;"vue"&lt;/code&gt;; the file extension has no say in it. You keep the framework's superpower and hand the &lt;em&gt;authoring&lt;/em&gt; problem to the most heavily-tooled language format in the industry.&lt;/p&gt;

&lt;p&gt;There are a few habits to pick up — slots become object literals of functions, &lt;code&gt;v-model&lt;/code&gt; becomes an explicit prop-plus-handler pair, and strongly-typed wrapper libraries sometimes want a props spread — but each is a small convention you learn in a day. All three make previously-implicit machinery explicit, which our code reviews turned out to appreciate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proof: TypeScript 7, in production, this week
&lt;/h2&gt;

&lt;p&gt;The compliance platform I work on is a Nuxt app with roughly eight hundred TSX components — pages, layouts, the app root, all of it plain TSX with &lt;code&gt;jsxImportSource: "vue"&lt;/code&gt;. Nuxt wires up the JSX transform automatically.&lt;/p&gt;

&lt;p&gt;Because every component is real TypeScript, our type-checking pipeline runs on the native toolchain — the same Go compiler that GA'd as TypeScript 7 — and has for months, across the preview releases. A full type-aware pass over the entire app takes about 46 seconds on a laptop. When the GA landed this week, our upgrade was a version bump in one &lt;code&gt;package.json&lt;/code&gt;. The components didn't know anything happened.&lt;/p&gt;

&lt;p&gt;Meanwhile the &lt;code&gt;.vue&lt;/code&gt; path, through no fault of its own, is on TypeScript 6 until the 7.1 API ships and Volar rebuilds on top of it — "at least several months," per the TypeScript team, with no committed date. I don't say that to gloat; the port will land and SFC users will get their speedup too. But the gap between the two paths was always there, and TypeScript 7 just made it visible as a version number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration, briefly
&lt;/h2&gt;

&lt;p&gt;We didn't start here. We started as a normal SFC codebase, and moved to TSX in one big-bang change this spring: about 1,500 files in a single PR. The mechanical bulk was done by a converter I open-sourced as &lt;a href="https://github.com/NikhilVerma/vue-sfc-to-tsx" rel="noopener noreferrer"&gt;&lt;code&gt;vue-to-tsx&lt;/code&gt;&lt;/a&gt; — it parses the SFC with Vue's own compiler and rewrites templates to JSX render functions, &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; macros to &lt;code&gt;defineComponent&lt;/code&gt;, scoped styles to plain CSS imports. Directives with no JSX equivalent get flagged for a human (or, optionally, an LLM) to resolve. It converts a file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"emit('update', count)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Count: &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initial&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="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;emit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;defineEmits&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&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;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&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="nx"&gt;initial&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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="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="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineComponent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;emits&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;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="nf"&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="nx"&gt;emit&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;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&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="nx"&gt;initial&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="k"&gt;return &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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    Count: &lt;span class="si"&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;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A handful of &lt;code&gt;.vue&lt;/code&gt; files survive in our repo — vendored UI-library primitives we treat as third-party code, plus a couple of components leaning hard on scoped styles. The goal was to move the codebase's center of gravity, and it moved.&lt;/p&gt;

&lt;p&gt;The migration's original motivation wasn't TypeScript 7 at all. We did it because we wanted components that read as plain code and tooling that didn't need a translation layer. The native-compiler dividend arrived months later, unearned, precisely &lt;em&gt;because&lt;/em&gt; we were sitting on the mainline TypeScript path when it got faster. That's the property I'd sell harder than any benchmark: standard formats appreciate in value while you sleep.&lt;/p&gt;

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

&lt;p&gt;Don't tie Vue to &lt;code&gt;.vue&lt;/code&gt;. The framework's best ideas — reactivity with automatic dependency tracking, effects the runtime manages for you — live in the runtime and travel intact into plain TSX. The single-file component remains a great format, and if it's serving you well, nothing here says to move.&lt;/p&gt;

&lt;p&gt;But if you've ever assumed Vue &lt;em&gt;can't&lt;/em&gt; work without its custom files, or dismissed it because you didn't want a bespoke toolchain between you and your type checker: that assumption is wrong. The path is documented and proven in production, and as of this week it comes with the fastest TypeScript compiler ever shipped. You can use Vue in a modern, native way — today.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>typescript</category>
      <category>tsx</category>
    </item>
    <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>
