<?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>Forem: Kevin Yank</title>
    <description>The latest articles on Forem by Kevin Yank (@sentience).</description>
    <link>https://forem.com/sentience</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F426620%2F9f59da45-4f40-41bf-b83e-912c55e60e71.jpg</url>
      <title>Forem: Kevin Yank</title>
      <link>https://forem.com/sentience</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sentience"/>
    <language>en</language>
    <item>
      <title>An Experiment with AI Assisted Web Development</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Tue, 30 Sep 2025 01:09:28 +0000</pubDate>
      <link>https://forem.com/cultureamp/an-experiment-with-ai-assisted-web-development-45gk</link>
      <guid>https://forem.com/cultureamp/an-experiment-with-ai-assisted-web-development-45gk</guid>
      <description>&lt;p&gt;As a productive learning exercise, I spent the last couple of evenings taking &lt;a href="https://cursor.com/home" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; (the desktop code editor that is a fork of &lt;a href="https://code.visualstudio.com" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt;) for a spin to build a new web app I needed for a side project – a system to manage charity raffle tickets at the 2025 &lt;a href="https://melbpenshow.com.au/" rel="noopener noreferrer"&gt;Melbourne Pen &amp;amp; Stationery Show&lt;/a&gt; I am helping to run soon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazdlpa11mhvexqekc7v6.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%2Fazdlpa11mhvexqekc7v6.png" alt="Screenshot of a web app, “Raffle Management System”, showing “Administrator” and “Ticket Holder” welcome messages" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(You can tell it's an AI-generated app by the not-quite-right icon above the word “Administrator”. 😅)&lt;/p&gt;

&lt;p&gt;High-level, coding an app from scratch with an agent like this enabled me to get something very usable, and at least at a surface level, fairly polished, in much less time than it would have taken me to do myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;I began with an empty repo and a &lt;strong&gt;README.md&lt;/strong&gt; file, where I described the features and tech stack that I had in mind, then I asked Cursor (using the Claude Sonnet 4 model) to implement the app described in that file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Raffle&lt;/span&gt;

A web application for issuing raffle tickets, recording ticket owners'
details, and removing raffle winners from the pool for future prize draws.

&lt;span class="gu"&gt;## Features&lt;/span&gt;

This app supports two separate interfaces for different user types:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Administrator UI**&lt;/span&gt;: Record and view ticket sales, and display a QR code
  for ticket buyers to enter their details.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Tickerholder UI**&lt;/span&gt;: Record ticket buyer details (name, phone number and
  email address), which will be used to contact them in the event one of
  their tickets wins in a prize draw. Ticket buyers will access this by
  means of the QR code in the Administrator UI. If the same person buys
  tickets more than once, they should be recorded as separate participants,
  and will need to enter their details separately for each purchase. We
  will not bother to match or merge separate purchases by the same.

Each ticket will have a unique alphanumeric code starting with A01 to A99,
then B01 to B99, and eventually Z01 to Z99. If more tickets are required,
ticket numbering will progress to two letters (AA01 to AA99, then AB01 to
AB99, and ultimately ZZ01 to ZZ99. In extreme cases ticket numbers can grow
to have three or even more letters (i.e. AAA01 to AAA99, and so on).

&lt;span class="gs"&gt;**Note**&lt;/span&gt;: The actual sales of tickets, and collection of payment for these,
are handled by a separate system (e.g. Square app), and are therefore out
of scope for this app.

&lt;span class="gu"&gt;### Administrator UI&lt;/span&gt;

Access requires a password shared by all raffle administrators, configured
as an environment variable.
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Create raffle**&lt;/span&gt;: Create a raffle with a name.

For each raffle:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Record sold tickets**&lt;/span&gt;: Create and allocate a specified number of
  tickets, the buyer details for which are initially pending. Display a QR
  code that the ticket buyer can scan to visit an unguessable URL (i.e.
  containing a UUID generated for their purchase) for the Ticketholder UI,
  which will enable them to enter their buyer details.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**View tickets sold**&lt;/span&gt;: View the total number of tickets sold, and a list
  of unique ticket numbers with the participant name for each (or
  "(pending)" if not yet recorded). Clicking the partcipant name should
  view the participant details (see below).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**View participants**&lt;/span&gt;: View a list of the participants' names, and the
  number of tickets purchased by each. Clicking the participant name should
  view the participant details (see below).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Export ticket list**&lt;/span&gt;: Export a list of eligible tickets (all sold
  tickets for which buyer details have been provided, minus those marked as
  winners) to a Google Sheet that can then be imported into
  wheelofnames.com for a prize draw. Each entry in the sheet should list
  the ticket holder name and the unique ticket number (e.g. "A53: Kevin
  Yank").

For each participant:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**View participant details**&lt;/span&gt;: View and edit collected name, phone number,
  email address.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**View tickets**&lt;/span&gt;: Show the unique ticket numbers assigned to the
  participant.

&lt;span class="gu"&gt;## Getting Started&lt;/span&gt;

&lt;span class="gu"&gt;### Prerequisites&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Node.js (v22 or higher)
&lt;span class="p"&gt;-&lt;/span&gt; npm package manager
&lt;span class="p"&gt;-&lt;/span&gt; PostgreSQL (v17 or late)

These dependencies can be installed in a contained environment by devbox
(&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;installation instructions&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://www.jetify.com/docs/devbox/installing_devbox/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;).
With devbox and direnv installed, simply open a shell in the project
directory and allow direnv to install the dependencies and open a shell
environment. Alternatively, without direnv, you can run these commands to
install the dependencies and open a shell yourself:&lt;span class="sb"&gt;

    # Install dependencies
    devbox install

    # Open a shell where these dependencies will be available
    devbox shell

&lt;/span&gt;&lt;span class="gu"&gt;### Local development installation&lt;/span&gt;&lt;span class="sb"&gt;

    # Clone the repository
    git clone &amp;lt;repository-url&amp;gt;
    cd raffle

    # Install dependencies
    npm install

    # Start the development server
    npm start

&lt;/span&gt;&lt;span class="gu"&gt;### Production deployment&lt;/span&gt;

The project is deployed to production with Docker Compose. A
docker-compose.json file is included, which creates a Next.js server
container and a PostgreSQL database container. The PostgreSQL database
container will persist its data to the data subdirectory in the project
root by default.

&lt;span class="gu"&gt;## Technology Stack&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Frontend**&lt;/span&gt;: Next.js (app router) with TypeScript
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Styling**&lt;/span&gt;: Tailwind CSS
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**State Management**&lt;/span&gt;: React Context API
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Testing**&lt;/span&gt;: Vitest and React Testing Library

&lt;span class="gu"&gt;## License&lt;/span&gt;

This project is licensed under the MIT License.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It did a pretty good job! The hardest part was getting Cursor not to do the whole thing in a single step, which would be impossible for me to review. Very quickly I stopped it and added some project rules to tell it to break its work down into small, discrete steps, and to stop after each one to allow me to review its changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A mix of "wow" and "wtf"
&lt;/h2&gt;

&lt;p&gt;Pretty quickly I noticed one thing it was doing not-so-well was picking NPM package versions. It set up a Next.js/React app with Tailwind, but seemed to choose arbitrary/random versions of these packages that were not up-to-date or necessarily even compatible with each other. When I told it to upgrade to the latest version of something, it chose a different, random version (sometimes even downgrading to an earlier version). Ultimately I got it to do a better job of this with another rule to tell it to install packages with the NPM CLI (where it could just ask for the latest version) instead of editing the &lt;strong&gt;package.json&lt;/strong&gt; directly (where it had to come up with a version number itself).&lt;/p&gt;

&lt;p&gt;The next thing I noticed is inconsistency in how "well" it implemented different things. Some things were surprisingly over-engineered, while other things were shockingly under-engineered. When it implemented password logins for my admin screens, it correctly used bcrypt to compare the submitted password with a stored hash of the correct password (and even conveniently let me configure the app with an un-hashed password in non-production environments), but then once the user's password was verified, &lt;strong&gt;it used a completely naive and totally insecure implementation for the login session&lt;/strong&gt;: setting a cookie with a plain text string (&lt;code&gt;admin-session=true&lt;/code&gt;) that could be easily faked by the user to gain admin access to the app.&lt;/p&gt;

&lt;p&gt;In basically every discrete step forward in building the app, the code it wrote was this unpredictable mix of "wow, great job!" and "wtf is this crap?". In most cases when I told it what I didn't like about what it had done, it did a good job of fixing it (e.g. it replaced the insecure session cookie with a secure JWT token implementation). Very quickly it felt like my role in the build became spotting and pointing out where it was being lazy/naive, which it almost always was in some way or another.&lt;/p&gt;

&lt;p&gt;Another example of a "mistake" I caught was in the data model: it designed a database schema that, while functional, required certain values to be duplicated across many records. (For the nerds: it had failed to normalize an important entity into its own table, and was instead storing an identical copy of it on each of its related entities.)&lt;/p&gt;

&lt;p&gt;On the bright side, one very nice thing that it did was it recognised a non-trivial piece of logic in my app (the numbering of raffle tickets) and proactively generated unit tests enforcing the requirements for this that I stated in my README.md. By reading the test cases it generated, I discovered a consequence of my spec that I didn't like, and I was able to correct this by updating the tests and then asking it to adjust the implementation and my spec in the README to match.&lt;/p&gt;

&lt;p&gt;Long story short, by forcing it to take small steps forward, reading every line of code, and acting as the "expert guardrails", I was able to make rapid progress towards a complete implementation of the app with many nice details that I hadn't considered or specified (like what should be on the home screen!) having very sensible defaults implemented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outsourcing visual design to the model
&lt;/h2&gt;

&lt;p&gt;The design of the UI in particular, for which I gave it almost no guidance (I just referred it to my Tailwind configuration and asked it to favor the green "brand" color I had defined there), is far preferable to the barely-styled thing I would have probably created myself in the name of expediency, and at the same time it looks like every other AI-generated app I've seen out there. There seems to be this "default" design language that is effectively baked into these models that is going to be the "default Twitter Boostrap theme" of the late 2020s.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06r5fc1x2cflkm5mbq1p.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%2F06r5fc1x2cflkm5mbq1p.png" alt="A screenshot of the web app's “Reacord Sold Tickets” screen, showing a QR code and instructions for a ticket buyer to claim their purchased tickets" width="800" height="958"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Side note: I'm eager to see how well MCP servers attached to design systems like &lt;a href="https://chakra-ui.com/docs/get-started/ai/mcp-server" rel="noopener noreferrer"&gt;Chakra UI&lt;/a&gt; end up working. How effectively can these models understand and leverage a specific design system?)&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't trust the model to troubleshoot
&lt;/h2&gt;

&lt;p&gt;Finally, once I got the app's features implemented, I was excited to get it deployed to a production environment (Docker Compose on a server I run in my home network). Getting this working took just as much time as building the app itself. The Docker environment it created to host my Next.js app was broken in several respects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it lacked a way to populate the initially-empty database,&lt;/li&gt;
&lt;li&gt;required directories (like the data directory for PostgreSQL) weren't created if they were missing,&lt;/li&gt;
&lt;li&gt;certain environment variables were defined in a way that caused dollar signs to trigger string interpolation, which completely broke the app's password authentication,&lt;/li&gt;
&lt;li&gt;some code that was generated correctly in local development (the Prisma database client that my app used to talk to its PostgreSQL server) was never generated in the production build, which produced TypeScript errors that the agent mis-diagnosed, and&lt;/li&gt;
&lt;li&gt;most frustratingly of all, the "Sign out" button in my app's admin UI was built in such a way that Next.js prefetched it as a web page in production, causing the user to be logged out immediately after logging in!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these issues were as baffling to me as they were to the model, but my experience meant I had much better instincts about how to identify and fix the problem. Invariably when I asked the model to try to solve it, it would attempt more and more extreme distortions of the project until I stopped it and suggested a different path. In some cases I suggested it check something it was overlooking, and that was enough to nudge it back onto a productive path. In other cases, my own half-baked theory turned out to be wrong, but the model treated it like absolute truth and therefore doubled and tripled down on incorrect solutions until I stopped it.&lt;/p&gt;

&lt;p&gt;Ultimately, in this final phase of the build, my own instincts and expertise became indispensable as the coding agent often had no hope of fixing these showstoppers, and if left alone would more or less destroy the project itself in its attempts to correct the problem.&lt;/p&gt;

&lt;p&gt;I have no doubt as models continue to grow, and we enable these agents to integrate with more of our tools (e.g. &lt;a href="https://developer.chrome.com/blog/chrome-devtools-mcp" rel="noopener noreferrer"&gt;to inspect the Network tab in Chrome's dev tools&lt;/a&gt; to see what a &lt;code&gt;set-cookie&lt;/code&gt; header returned by my app looks like, or to see the unexpected call to the "logout" API), it will be better able to understand and fix issues like these, but right now deployment and infrastructure automation felt like an area where the tool could code productively but definitely needed human help to find and diagnose its mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When robots conspire
&lt;/h2&gt;

&lt;p&gt;Two more quick anecdotes from this adventure:&lt;/p&gt;

&lt;p&gt;At one point I asked the agent to reset my local development database. It attempted to do so, at which point the database abstraction library (Prisma) detected that it was being called by Cursor and output a stern warning to the agent saying that it must make 100% sure that the user (me) understands what is about to happen, because deleting a database can be disastrous in a production environment (see &lt;a href="https://www.prisma.io/docs/orm/more/ai-tools/cursor#ai-safety-guardrails-for-destructive-commands" rel="noopener noreferrer"&gt;AI Safety guardrails for destructive commands&lt;/a&gt; in the Prisma docs). The agent then generated a huge wall of text for me, detailing the risks of deleting databases, and describing how doing this kind of thing accidentally has destroyed companies and livelihoods, and was I absolutely, 100% sure that it should proceed. "Please type a clear and simple 'yes' if I should go ahead," it concluded. I typed "yes", it re-ran the command with a special environment variable (&lt;code&gt;PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION&lt;/code&gt;), and it did it. This combination of automated systems conspiring to protect me felt both magical and terrifying.&lt;/p&gt;

&lt;p&gt;At another point, while diagnosing the issue with unwanted string interpolation in my environment variables, I asked the agent to help me figure out why the value I specified in my app's &lt;strong&gt;.env&lt;/strong&gt; file was appearing mangled when it was accessed inside my Next.js app. Cursor noted that "for security reasons, I am not allowed to view the contents of your &lt;strong&gt;.env&lt;/strong&gt; file" (a nice guardrail, since this file will often contain security credentials!), but then without asking &lt;strong&gt;hacked around the guardrail itself&lt;/strong&gt; by running a &lt;code&gt;grep&lt;/code&gt; of the file's contents to see into it. This was definitely terrifying! 😱&lt;/p&gt;

&lt;h2&gt;
  
  
  My impressions of Cursor
&lt;/h2&gt;

&lt;p&gt;Oh, and after all this, what do I think of Cursor?&lt;/p&gt;

&lt;p&gt;My high-level impression is that, based on my experience so far, Cursor is not much better than GitHub Copilot Agent Mode in VSCode, and in some ways it's worse.&lt;/p&gt;

&lt;p&gt;Cursor claims several unique features (e.g. magical Tab completion that predicts the location of the next change you want to make to your code and takes you there) that sound nice, but in practice I either didn't end up using them, or they got in my way.&lt;/p&gt;

&lt;p&gt;Cursor was also buggy at times in ways that look to me like symptoms of it being a somewhat quick-and-dirty fork of VS Code. E.g. at one point it incorrectly told me that I was running the wrong version of Cursor for my CPU architecture, and when it offered to download the correct version, it actually downloaded a copy of VS Code instead!&lt;/p&gt;

&lt;p&gt;I want to play with Cursor a little more to see if some of its unique features come in handy when I try to do some more advanced things (like background agents), but I'm currently trending towards striking it off my list of preferred tools.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How to Build an Agent in JavaScript</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Fri, 15 Aug 2025 05:44:58 +0000</pubDate>
      <link>https://forem.com/cultureamp/how-to-build-an-agent-in-javascript-2n75</link>
      <guid>https://forem.com/cultureamp/how-to-build-an-agent-in-javascript-2n75</guid>
      <description>&lt;p&gt;This is a talk I gave at &lt;a href="https://melbjs.com/" rel="noopener noreferrer"&gt;MelbJS&lt;/a&gt; on 13 August 2025.&lt;/p&gt;

&lt;p&gt;Here’s a video version:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/rEf2-VC2jEI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sentience/how-to-build-an-agent-in-javascript" rel="noopener noreferrer"&gt;The final source code of the sample project is here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you prefer reading, read on! The following is a lightly-edited transcript of the video above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sq2sqf1w000vy1t20hd.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%2F4sq2sqf1w000vy1t20hd.png" alt="title slide: How to Build an Agent in JavaScript, by Thorsten Ball × Kevin Yank"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see on my title slide here that I'm also crediting Thorsten Ball because in the spirit of AI technology, I borrowed Thorsten's blog post without permission in order to help me generate this talk. I hope he doesn't mind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ht6rdqz4xi251blete9.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%2F3ht6rdqz4xi251blete9.png" alt="screenshot of a blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the blog post in question, &lt;a href="https://ampcode.com/how-to-build-an-agent" rel="noopener noreferrer"&gt;How to Build an Agent, or: The Emperor Has No Clothes&lt;/a&gt;. It's on the Amp Blog. Amp is an agentic coding tool from Sourcegraph. And this blog post came to my attention at the Web Directions Code Leaders conference in Melbourne in early 2025, where speaker Geoffrey Huntley, who had recently gone to work at Amp, mentioned this as, for his money, the blog post of the year.&lt;/p&gt;

&lt;p&gt;He said, if you were trying to get your head around or come to grips with this, this new LLM powered coding tool landscape, that this was the blog post to read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pj8qhnevls2xqd8zi7u.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%2F4pj8qhnevls2xqd8zi7u.png" alt="six product logos"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when we talk about those tools, we're talking about these things. This is the logos of Cursor, Zed, Windsurf, Amp, Claude Code and GitHub Copilot. And just because of how fast these things are moving right now, by the time I publish this video, it's likely there are two more logos that belong on this slide and maybe one of these companies is already out of business.&lt;/p&gt;

&lt;p&gt;That's how fast these things are going, and that speed of evolution is one of the factors that makes this kind of technology really intimidating. It seems to work by magic. It's progressing faster than our ability to understand how it works. But, the point of this blog post is that building a small and yet highly impressive agent doesn't even require elbow grease.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckifhx8sshhsx6ls6tck.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%2Fckifhx8sshhsx6ls6tck.png" alt="quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can do it in less than 400 lines of code, says Thorsten, most of which is boilerplate. I'm going to show you how right now we're going to write some code together and go from zero lines of code to, oh wow, this is a game changer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pencils Out!
&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.amazonaws.com%2Fuploads%2Farticles%2F1necyjxo3eq8p5yam4br.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%2F1necyjxo3eq8p5yam4br.png" alt="quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And so the blog post invites us to get our pencils out and to follow along, and it starts by creating a new Go project.&lt;/p&gt;

&lt;p&gt;Now, this is where it, uh, it, uh, presented a bit of a speed bump to me because Go is not a language I know well, or really even at all. I mean, I know what it is. I know what it looks like. I've never used it to build anything so casually using it to explore a new technology would be like placing a challenge in front of a challenge.&lt;/p&gt;

&lt;p&gt;And so looking at this, I thought, you know what? I am going to try to follow along in a language that I do know well, which, for today's purposes will be JavaScript written in TypeScript. And so that's what I've done. With this talk, I have translated ths blog post, which is excellent, but I've changed all of the code into TypeScript, for an audience that knows that language well.&lt;/p&gt;

&lt;p&gt;So let's get started. This is the index.js file, the startup file for a Node.js command line app:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AnthropicVertex&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;@anthropic-ai/vertex-sdk&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;readline&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;readline/promises&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;Agent&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;./agent/agent.js&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;projectId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cross-camp-ai-enablement&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;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;us-east5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AnthropicVertex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;region&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;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create readline interface for user input&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInterface&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Prompt user for input&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userMessage&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;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u001b[94mYou&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u001b[0m: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Close the readline interface&lt;/span&gt;
  &lt;span class="nx"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;userMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;showAgentMessage&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="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`\n\u001b[93mClaude\u001b[0m: &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="s2"&gt;\n`&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;It has a main function here, and the first thing that function does when it starts up is it creates a client for the Anthropic Vertex service. So Anthropic is the company that makes the Claude LLM models, and Vertex is Google's hosting of LLM models. It's its service for providing access to LLM models.&lt;/p&gt;

&lt;p&gt;So the Anthropic Vertex client lets me use the Anthropic models on the Google Vertex service. This happens to be the most convenient way for me to access a Claude model programmatically at Culture Amp because we use Google Vertex to host our, uh, our models for development purposes. I specify the project ID and the region, and it will get my credentials from the environment.&lt;/p&gt;

&lt;p&gt;But basically I have created a connection to the Anthropic LLM model here as a client. And then I pass that client along with two other things to this &lt;code&gt;Agent&lt;/code&gt; object there or class. This is a class that we are going to create. It is the body of our program, and we are going to instantiate that class, and then on the resulting agent object, we are going to call the &lt;code&gt;run&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;So this &lt;code&gt;Agent&lt;/code&gt; class takes three things, the Anthropic client that we've provided, a &lt;code&gt;getUserMessage&lt;/code&gt; function and a &lt;code&gt;showAgentMessage&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;getUserMessage&lt;/code&gt; is doing some pretty standard Node.js stuff to present a prompt to the user with some nice shell colors. And when the user types a message and hits Enter, it will get that in this user message string, which it will return.&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;showAgentMessage&lt;/code&gt; is really just a &lt;code&gt;console.log&lt;/code&gt;, again using some escape sequences here to present responses from Claude in a nicely colored header.&lt;/p&gt;

&lt;p&gt;So we pass those three things into the &lt;code&gt;Agent&lt;/code&gt; and then we run it. So let's look at the &lt;code&gt;Agent&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AnthropicVertex&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;@anthropic-ai/vertex-sdk&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;Anthropic&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;@anthropic-ai/sdk&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;BadRequestError&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;@anthropic-ai/vertex-sdk/core/error.mjs&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;class&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnthropicVertex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;getUserMessage&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="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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;async&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO&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;This is our agent.ts file. It is as promised a TypeScript class with a constructor that takes those three things, the &lt;code&gt;client&lt;/code&gt;, the &lt;code&gt;getUserMessage&lt;/code&gt;, the &lt;code&gt;showAgentMessage&lt;/code&gt;, and then it will have a &lt;code&gt;run&lt;/code&gt; method, which to begin with just says, &lt;code&gt;TODO&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, what are we gonna make this &lt;code&gt;Agent&lt;/code&gt; do?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nhaynbabz8silcp0jfq.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%2F1nhaynbabz8silcp0jfq.png" alt="a diagram of the chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, the first thing we wanna make it do is to provide a chat experience. This is how we will give our agent instructions and receive feedback back from that agent. So we would like for example, to say, Hi Claude, my name is Kevin, and have it send that up to the Claude model. The Claude model might reply with a message that we want to present on screen.&lt;/p&gt;

&lt;p&gt;Hello, Kevin. It's nice to meet you. How are you doing today? Is there something I can help you with?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsr67ge5z6c66xpqhjz39.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%2Fsr67ge5z6c66xpqhjz39.png" alt="a diagram of the chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then we might respond with what's my name, just to test if it's paying attention, but out of the box it might say something like, I don't have access to your personal information, including your name, unless you've shared it with me in our conversation.&lt;/p&gt;

&lt;p&gt;And this will happen because all of these LLM services are completely stateless, so every request you send to it is completely separate. It has no memory from one request to the next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiij6qzvr7srocxft58qi.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%2Fiij6qzvr7srocxft58qi.png" alt="a diagram of the chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As web developers, this should be familiar. This is how web servers work by default as well. So if we want to carry on a conversation, we are going to need to provide it some way to remember the conversation so far.&lt;/p&gt;

&lt;p&gt;So what we do instead to prevent it from saying New phone, who dis? is, when we send our first message and it replies back, we will take that reply into a record of the conversation so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs937g4zoe2hlq7650vb5.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%2Fs937g4zoe2hlq7650vb5.png" alt="a diagram of the chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So this is a reminder. You said this, and now I'm gonna ask this follow-up question. And when we send this question to the LLM, we actually send the entire conversation so far as a list of messages and then it is able to reply, your name is Kevin, as you mentioned in your introduction.&lt;/p&gt;

&lt;p&gt;This is how all LLM powered chat experiences work. The longer you chat with them, the more context you need to send, the larger the record of the conversation so far gets every time you send it with your next message. And this is one reason why chats get more and more expensive in the number of tokens they use, the longer that they run.&lt;/p&gt;

&lt;p&gt;So how do we implement this back and forth in our &lt;code&gt;run&lt;/code&gt; function?&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;async&lt;/span&gt; &lt;span class="nf"&gt;run&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;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chat with Claude (use 'ctrl-c' to quit)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;conversation&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="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;conversation&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messageToMessageParam&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="k"&gt;for &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;message&lt;/span&gt; &lt;span class="k"&gt;of&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;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;switch &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="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;showAgentMessage&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="k"&gt;break&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&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="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-3-7-sonnet@20250219&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conversation&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;private&lt;/span&gt; &lt;span class="nf"&gt;messageToMessageParam&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="nx"&gt;Anthropic&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;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&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;role&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="nx"&gt;role&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="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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;Well, we will start by creating this &lt;code&gt;conversation&lt;/code&gt; list. So it will be an empty array of Anthropic messages. Ut's empty because the conversation has yet to begin. We'll display an introductory message saying, Control-C to quit, and then we go into this &lt;code&gt;while&lt;/code&gt; &lt;code&gt;true&lt;/code&gt; block. So while a &lt;code&gt;while&lt;/code&gt; &lt;code&gt;true&lt;/code&gt; loop will just run over and over again until the user exits the program with Control C.&lt;/p&gt;

&lt;p&gt;We will then begin by getting the user's first message, using that &lt;code&gt;getUserMessage&lt;/code&gt; method that we passed into the constructor. And when we get that message, we will bundle it up into this object with a &lt;code&gt;role&lt;/code&gt; &lt;code&gt;user&lt;/code&gt; and the &lt;code&gt;content&lt;/code&gt; of the message. This is what these Anthropic messages need to look like. And we will push it into the &lt;code&gt;conversation&lt;/code&gt;. The &lt;code&gt;conversation&lt;/code&gt; will begin with the user's first message.&lt;/p&gt;

&lt;p&gt;We then pass the entire &lt;code&gt;conversation&lt;/code&gt; into this &lt;code&gt;runInference&lt;/code&gt; method, which we have implemented. It's a very short method. All it does is send the &lt;code&gt;conversation&lt;/code&gt; in its entirety to the Claude model using the &lt;code&gt;client&lt;/code&gt; that was passed into the constructor.&lt;/p&gt;

&lt;p&gt;We specify Claude 3.7 Sonnet here, and we say that we want it to reply with a maximum of 1,024 tokens.&lt;/p&gt;

&lt;p&gt;That will result in a response from the LLM, which we receive in our &lt;code&gt;while&lt;/code&gt; loop.&lt;/p&gt;

&lt;p&gt;First of all, we translate that response into that same &lt;code&gt;MessageParam&lt;/code&gt; object format, which we do with this &lt;code&gt;messageToMessageParam&lt;/code&gt; function. Nothing too complicated going on there.&lt;/p&gt;

&lt;p&gt;And then we push that into our &lt;code&gt;conversation&lt;/code&gt;. Our &lt;code&gt;conversation&lt;/code&gt; now includes our request and the initial response. We then go through the messages in the response and look for any that are a &lt;code&gt;text&lt;/code&gt; message from the LLM, and any text messages that we get from the LLM we will display to the user with &lt;code&gt;showAgentMessage&lt;/code&gt;, that function that was passed into our agent's constructor with the message text.&lt;/p&gt;

&lt;p&gt;And that's it. Uh, we then start the &lt;code&gt;while&lt;/code&gt; loop over again by asking the user to contribute another message to the conversation. And on it goes.&lt;/p&gt;

&lt;p&gt;Let's demo this. I've got all of this code so far in my project here, and I'm gonna run it with &lt;code&gt;pnpm agent&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo1&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; My name is Kevin Yank.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Hello, Kevin Yank! It's nice to meet you. Is there something I can help you with today?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; Suggest some nicknames for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Here are some potential nicknames for Kevin Yank:&lt;/p&gt;

&lt;p&gt;• Kev \&lt;br&gt;
• K.Y. \&lt;br&gt;
• Yanker \&lt;br&gt;
• K-Yank \&lt;br&gt;
• Kevster \&lt;br&gt;
• Captain K \&lt;br&gt;
• The Yank \&lt;br&gt;
• K-Dog \&lt;br&gt;
• Yanky \&lt;br&gt;
• Kevbo&lt;/p&gt;

&lt;p&gt;These are just suggestions - nicknames usually work best when they reflect your personality or come about naturally from friends and family!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, let's say my name is Kevin Yank.&lt;/p&gt;

&lt;p&gt;It creates that conversation list, sends it to the LLM, receives the response, adds the response to the conversation, and then displays it on the screen. Hi, Kevin Yank. Nice to meet you. Okay, great. Now let's test if it remembers my name.&lt;/p&gt;

&lt;p&gt;I'll ask it to suggest some nicknames for me. Just a little risky. Let's see what it comes up with. All right. There we go. Uh, Captain K, that's a new one. No one has ever called me Captain K. Okay. Um, yeah. Interesting.&lt;/p&gt;

&lt;p&gt;Let's, uh, move on from that very swiftly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8alsbiq63e04ct8rem3u.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%2F8alsbiq63e04ct8rem3u.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the blog post says, okay, let's move on, because the nicknames suck.&lt;/p&gt;

&lt;p&gt;They do indeed.&lt;/p&gt;

&lt;p&gt;And this is not an agent yet. What is an agent? Here is Thortsten's definition: an LLM with access to tools, giving it the ability to modify something outside the context window.&lt;/p&gt;

&lt;p&gt;The context window is the technical name for that conversation list. It is the window of things that we will remind the LLM of every time we send it a request.&lt;/p&gt;

&lt;p&gt;Right now, all the LLM is empowered to do is to contribute messages to that list. So giving it tools that let it do other things is how we will turn it into an agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frubow09tph7uxscelruy.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%2Frubow09tph7uxscelruy.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's think about our first tool. What is a tool? Well, it says the basic idea is this. You send a prompt to the model that says it should reply in a certain way. If it wants to use a tool, then you as the receiver of that message, use the tool by executing it and replying with the result.&lt;/p&gt;

&lt;p&gt;That's it. Everything else we'll see is just an abstraction on top of this concept.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fviq9t24rqa1hb2n7zt7r.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%2Fviq9t24rqa1hb2n7zt7r.png" alt="a diagram of the conversation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So replaying this in our diagram of the conversation, we might start the conversation by saying, in the following conversation, let me know if you want to use one of these tools, and then we list all the tools that are available. Then blah, blah, blah. We have a conversation, and at a certain point, the LLM may reply with, I want to use this tool,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklfbc2rs89qfd6jvxxnv.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%2Fklfbc2rs89qfd6jvxxnv.png" alt="a diagram of the conversation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;in which case, as usual, we record that as a message in the conversation. We then follow its request. We run the tool for it and add the response or the result of running that tool to the conversation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdly0nsvx2h6mvbyrku5o.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%2Fdly0nsvx2h6mvbyrku5o.png" alt="a diagram of the conversation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We then send the conversation back to the LLM as usual, and it will continue with the knowledge of what resulted from its tool call. That's it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ezfnb9qh4o207nmlkjt.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%2F5ezfnb9qh4o207nmlkjt.png" alt="“In the following conversation, wink if you want me to raise my arm.”"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In his blog post, Thorston says, this is like when you're talking to a human, you might say in the following conversation, wink if you want me to raise my arm. And in a funny sort of way, you have now given that other human the power to raise your arm. They can't literally raise your arm, but they can ask you to and you will do it for them.&lt;/p&gt;

&lt;p&gt;And that is how an agent works. We tell the LLM some things we are willing to do for it, and then it will let us know when it wants us to do those things.&lt;/p&gt;

&lt;p&gt;So we can actually demonstrate this with the code we've already written.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; When I ask you about the weather in a given location, I want you to reply with &lt;code&gt;get_weather(&amp;lt;location_name&amp;gt;)&lt;/code&gt;. I will then tell you what the weather in that location is. Understood?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I understand. When you ask me about the weather in a specific location, I'll respond with &lt;code&gt;get_weather(&amp;lt;location_name&amp;gt;)&lt;/code&gt;. Then you can provide me with the weather information for that location, and we can continue our conversation from there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; What's the weather in Melbourne, Australia?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; get_weather(Melbourne, Australia)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; Sunny and 15 degrees C&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Thank you for providing that information. It sounds like a pleasant day in Melbourne, Australia - sunny with a temperature of 15 degrees Celsius. That's what I'd consider mild weather - cool enough to need a light jacket but with nice sunshine. Perfect for spending some time outdoors if you have the opportunity!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So back in our conversation about the nicknames, I'm gonna say, when I ask you about the weather in a given location, I want you to reply with &lt;code&gt;get_weather(&amp;lt;location_name&amp;gt;)&lt;/code&gt;. I will then tell you what the weather in that location is. Understood?&lt;/p&gt;

&lt;p&gt;So we're teaching it about a tool. It says, I understand. So let's try it out.&lt;/p&gt;

&lt;p&gt;What's the weather in Melbourne, Australia?&lt;/p&gt;

&lt;p&gt;That's where I happen to be, on the land of the Wurundjeri Woi Wurrung people.&lt;/p&gt;

&lt;p&gt;It says &lt;code&gt;get_weather&lt;/code&gt;. So it is actually sending us a tool call.&lt;/p&gt;

&lt;p&gt;So we would now go to the Bureau of Meteorology website, look up the weather and respond. It's sunny and 15 degrees C.&lt;/p&gt;

&lt;p&gt;Thank you for letting me know. Sunny and 15 degrees Celsius in Melbourne, Australia. Anyway, it goes on. It asked us to look up the thing, the answer to the question we asked it, we looked up the answer for it. It then repeated that answer because it now has that knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp6346u7lcpblabhcigsd.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%2Fp6346u7lcpblabhcigsd.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is how a tool works in an AI agent. Thorston in his blog post says That worked very well on first try, didn't it? These models are trained and fine tuned to use tools, and they're very eager to do so. By now in 2025, they kind of know that they don't know everything and can use tools to get more information.&lt;/p&gt;

&lt;p&gt;Of course, that's not precisely what's going on, but it's a good enough explanation for now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t2phkso66bk5zwkosmf.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%2F3t2phkso66bk5zwkosmf.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To summarize, all there is to tools and tool use are two things: You tell the model what tools are available. Then when the model wants to execute the tool, it tells you, you execute the tool and send the response up.&lt;/p&gt;

&lt;p&gt;To make step one easier, the big model providers have built-in APIs to send tool definitions along with your requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;read_file&lt;/code&gt; tool
&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.amazonaws.com%2Fuploads%2Farticles%2F4qt13syefmwokt9vmqfj.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%2F4qt13syefmwokt9vmqfj.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's actually build a tool in our code. This will be the &lt;code&gt;read_file&lt;/code&gt; tool. And in order to define the &lt;code&gt;read_file&lt;/code&gt; tool, we're going to use the types that the Anthropic SDK suggests, but keep in mind, under the hood, this will all end up as strings that are sent to the model.&lt;/p&gt;

&lt;p&gt;It's all wink if you want me to use &lt;code&gt;read_file&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&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;@anthropic-ai/sdk&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;type&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&lt;/span&gt; &lt;span class="o"&gt;=&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&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;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InputSchema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;func&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&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;So the first thing we'll do is because this is a TypeScript program, we will write a type that defines what a &lt;code&gt;ToolDefiniton&lt;/code&gt; is. A tool is defined by three things: a &lt;code&gt;name&lt;/code&gt;, which is a string, a &lt;code&gt;description&lt;/code&gt;, the instructions for what that tool does, and when the model should use it, and an &lt;code&gt;input_schema&lt;/code&gt;. This is a schema that says what arguments or parameters are required when sending a request to run this tool.&lt;/p&gt;

&lt;p&gt;And the last thing is the function. That's not there for the LLM; that's there for us. When the LLM asks us to run this tool, this is the function that we will call to do the work for it. It will return either a string or a promise of a future string if it needs to do anything asynchronous.&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnthropicVertex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;getUserMessage&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="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;showToolMessage&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&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;private&lt;/span&gt; &lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&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="na"&gt;anthropicTools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ToolUnion&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;tools&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;tool&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;tool&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="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_schema&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&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="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-3-7-sonnet@20250219&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;anthropicTools&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;So then in our &lt;code&gt;Agent&lt;/code&gt;, we will add a couple of things to the constructor. We will have a &lt;code&gt;showToolMessage&lt;/code&gt;. This is something that we will call when we are, um, when the LLM sends us a message to run a tool. And finally a list of &lt;code&gt;tools&lt;/code&gt;, which is an array of these &lt;code&gt;ToolDefinition&lt;/code&gt; objects.&lt;/p&gt;

&lt;p&gt;Our &lt;code&gt;runInference&lt;/code&gt; function that we use to send requests to the LLM gets a little bit bigger. Before we were just sending the conversation, but now we are also sending this list of tools and that list of tools we build up by taking our list of tool definitions, and basically stripping off those functions, which I mentioned are just for us to use. We're passing along just the &lt;code&gt;name&lt;/code&gt;, the &lt;code&gt;description&lt;/code&gt;, and the &lt;code&gt;input_schema&lt;/code&gt;. Those three things are all that the model needs to know about to use our tools.&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;import&lt;/span&gt; &lt;span class="nx"&gt;tools&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;./agent/tools/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;⋮&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;showToolMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tools&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="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;showToolMessage&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="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`\n\u001b[92mTool\u001b[0m: &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="s2"&gt;\n`&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 in our &lt;code&gt;main&lt;/code&gt; function at the top of our program, we need to pass these two new things, &lt;code&gt;showToolMessage&lt;/code&gt;, and &lt;code&gt;tools&lt;/code&gt; to the &lt;code&gt;Agent&lt;/code&gt; constructor.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;showToolMessage&lt;/code&gt; is just another &lt;code&gt;console.log&lt;/code&gt; function that provides some nice formatting in a different color, and the list of tools we are importing from another file that looks like this.&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileTool&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;./readFile.js&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;readFileTool&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just an export of a array with one item in it. So we have only one tool so far. It is the &lt;code&gt;readFileTool&lt;/code&gt; that again, we import from another file and now we're ready to actually write that tool.&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;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs/promises&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;existsSync&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;fs&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;nodePath&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;path&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;ToolDefinition&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;../types.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Tool for reading file contents
 */&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;readFileTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&lt;/span&gt; &lt;span class="o"&gt;=&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;read_file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Read the contents of a given relative file path.
     Use this when you want to see what's inside a
     file. Do not use this with directory names.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`The relative path of a file in
                      the working directory.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&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;func&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="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;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="na"&gt;path&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;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&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;resolvedPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if file exists&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="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;))&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;`File not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&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="k"&gt;try&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;Here's what it looks like. Um, there is not very much code here, it is just a big object with those fields. We described the &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt; the &lt;code&gt;description&lt;/code&gt;, which is the instructions to the LLM. The &lt;code&gt;input_schema&lt;/code&gt;, which says that there is a path argument that is required. It is a string, which is the relative path of a file in the working directory.&lt;/p&gt;

&lt;p&gt;And then over on the right hand side is our function. This &lt;code&gt;func&lt;/code&gt;, again, is the thing that we will call when we need to do the work of this tool. And its contents is really just plain Node.js file system stuff. We check if the file exists here with &lt;code&gt;existsSync&lt;/code&gt;, and if it does, we read the file as a UTF-8 text file and return its contents as a string.&lt;/p&gt;

&lt;p&gt;That's it. That's our tool for reading file contents.&lt;/p&gt;

&lt;p&gt;So with that code added to our project, let's try it out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo2&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; What's in src/agent.ts?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll check what's in the src/agent.ts file for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm going to check out this version and run the agent. And ask it what's in src/agent.ts, which is that top level file with the main function in it. I'll check the contents of the file for you, and then nothing happens.&lt;/p&gt;

&lt;p&gt;Do you know why nothing happens? Well, the LLM is winking at us, but we are not yet paying attention to winks.&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;async&lt;/span&gt; &lt;span class="nf"&gt;run&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;conversation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chat with Claude (use 'ctrl-c' to quit)&lt;/span&gt;&lt;span class="dl"&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;readUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readUserInput&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;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessageParam&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;conversation&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="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runInference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;conversation&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;messageToMessageParam&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="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;toolResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContentBlockParam&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="k"&gt;for &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;message&lt;/span&gt; &lt;span class="k"&gt;of&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;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;switch &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="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;showAgentMessage&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_use&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeTool&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="nx"&gt;id&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="nx"&gt;name&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="nx"&gt;toolResults&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="nx"&gt;result&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolResults&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;span class="nx"&gt;readUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;readUserInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="nx"&gt;conversation&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="nx"&gt;toolResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&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;So back in our code, there's something more we need to add here. This is our &lt;code&gt;run&lt;/code&gt; function for the &lt;code&gt;Agent&lt;/code&gt;. And inside of our &lt;code&gt;while&lt;/code&gt; loop, we were previously responding to text messages by showing an agent message on the screen.&lt;/p&gt;

&lt;p&gt;There is now a second kind of message that we need to recognize from the model, and that's a &lt;code&gt;tool_use&lt;/code&gt; message. And when that kind of message comes in, we will call our &lt;code&gt;executeTool&lt;/code&gt; method that I'll show you on the next slide. We pass in the request to execute a tool and get a result back. That result, we will push into a &lt;code&gt;toolResults&lt;/code&gt; array, and then after processing all the messages from the LLM, if there are any tool results, then we can skip prompting the user for the next message because the next message that we wanna send to the LLM is the result of running the tool. We don't need to also prompt the user to type something in. We push all all of the tool results into the &lt;code&gt;conversation&lt;/code&gt; list and go back to the start of the &lt;code&gt;while&lt;/code&gt; loop.&lt;/p&gt;

&lt;p&gt;That's it. That's everything that's changed here.&lt;/p&gt;

&lt;p&gt;Now let's look at the &lt;code&gt;executeTool&lt;/code&gt; method.&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;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeTool&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContentBlockParam&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;tool&lt;/span&gt; &lt;span class="o"&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;tools&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;t&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;t&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="nx"&gt;name&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;tool&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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;`tool not found`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;showToolMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolDescription&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does is it looks through our list of tools to find the one with a name that matches the one requested by the model. If it doesn't find such a tool, we will respond with this error object that the language model will recognize.&lt;/p&gt;

&lt;p&gt;But if we do find the tool that was requested, then we create a string here to describe the tool request and show it on the screen to the user. And then the last thing we do is actually call the tool function with the provided inputs and put its result in this content field of this object, which is a tool result object that will go into the conversation record and be sent back to the LLM.&lt;/p&gt;

&lt;p&gt;All right. Our missing pieces are in place now. Let's try the demo again.&lt;/p&gt;

&lt;p&gt;Oh, and before I. Ask it to do something. I'm gonna set something up here. This is, uh, a fun little thing that Thorston has in his blog post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo3&lt;/p&gt;

&lt;p&gt;❯ echo 'what animal is the most disagreeable because it always says neigh?' &amp;gt;&amp;gt; secret-file.txt&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He's gonna create a secret-file.txt file on my hard drive, and it contains a riddle. What animal is the most disagreeable because it always says neigh?&lt;/p&gt;

&lt;p&gt;Now let's run the agent and ask it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo3&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; Claude, buddy, help me solve the riddle in the secret-file.txt file&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll help you solve the riddle in the secret-file.txt file. Let me first read its contents to see what we're working with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; read_file({"path":"secret-file.txt"})&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I found the riddle! It's a classic wordplay riddle:&lt;/p&gt;

&lt;p&gt;"What animal is the most disagreeable because it always says neigh?"&lt;/p&gt;

&lt;p&gt;The answer is a &lt;strong&gt;horse&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;This is a pun-based riddle where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horses make a sound that we call a "neigh"&lt;/li&gt;
&lt;li&gt;"Neigh" sounds like "nay," which is a way of saying "no" or disagreeing&lt;/li&gt;
&lt;li&gt;So horses are "disagreeable" because they're always saying "neigh/nay"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Was there anything else you wanted to know about this riddle?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude, buddy, help me solve the riddle in the secret-file.txt file&lt;/p&gt;

&lt;p&gt;It is going to read the file and there you go. It reads the riddle and comes up with the answer. The answer to this riddle is a horse, and at this point, Thorsten invites us to say, holy crap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0t2ir3ww5qbupowmwvv9.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%2F0t2ir3ww5qbupowmwvv9.png" alt="holy crap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, he uses slightly spicier language into the blog post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5klwww894e6fpndlxes.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%2Fe5klwww894e6fpndlxes.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take a deep breath and say it together. Ready? Here we go. Holy shit. You just give it a tool and it uses it when it thinks it'll help solve the task. Remember, we didn't say anything about if a user asks you about a file, read the file. We also didn't say if someone looks, if something looks like a file name, figure out how to read it. No, none of that.&lt;/p&gt;

&lt;p&gt;We say, help me solve the thing in this file, and Claude realizes that it can read the file to answer that, and off it goes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detour: human in the loop
&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.amazonaws.com%2Fuploads%2Farticles%2F9f4f2gjz9d2mw21e5m43.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%2F9f4f2gjz9d2mw21e5m43.png" alt="detour: human in the loop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I got a little freaked out because I realized this program was giving access to read any file on my hard drive to this language model that I maybe shouldn't trust.&lt;/p&gt;

&lt;p&gt;And so I'm gonna take a slight detour here from Thorsten's blog post and talk about human in the loop. I want to approve all of these tool calls because already they're doing something potentially dangerous that I wanna make sure seems sensible to me.&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;import&lt;/span&gt; &lt;span class="nx"&gt;tools&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;./agent/tools/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;⋮&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getUserMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;getToolConsent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tools&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="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getToolConsent&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&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;rl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInterface&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&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;consent&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;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`\n\u001b[92mTool request\u001b[0m: &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="s2"&gt;\n`&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u001b[93mClaude&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;u001b[0m: Continue? [yes]: &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="nx"&gt;rl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;consent&lt;/span&gt; &lt;span class="o"&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;consent&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of the &lt;code&gt;showToolMessage&lt;/code&gt; function, we are going to pass to our &lt;code&gt;Agent&lt;/code&gt; a &lt;code&gt;getToolConsent&lt;/code&gt; function, which is a little more strenuous.&lt;/p&gt;

&lt;p&gt;Instead of just doing a &lt;code&gt;console.log&lt;/code&gt;, it is going to ask the user, here's the tool request. Do you want to continue? Type yes or hit enter to continue. And if the user does anything other than hit enter or type yes, we will refuse to run the tool.&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AnthropicVertex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;getUserMessage&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="nb"&gt;Promise&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;showAgentMessage&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;getToolConsent&lt;/span&gt;&lt;span class="p"&gt;:&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&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;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeTool&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContentBlockParam&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;tool&lt;/span&gt; &lt;span class="o"&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;tools&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;t&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;t&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="nx"&gt;name&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;tool&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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;`tool not found`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&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="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToolConsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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;`User did not consent to tool execution`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;tool_use_id&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's what this looks like here in our, uh, execute tool method.&lt;/p&gt;

&lt;p&gt;If the user doesn't give tool consent, we return a tool result that's an error saying the user did not consent to tool execution. Alright, so let us see how this works.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo4&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; Claude, help me solve the riddle in secret-file.txt&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll help you solve the riddle in secret-file.txt. First, let me read the file to see what the riddle contains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"secret-file.txt"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]: no&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I apologize, but I need your permission to use the file reading tool to access the content of secret-file.txt. Once you give your consent, I'll be able to read the file and help you solve the riddle it contains.&lt;/p&gt;

&lt;p&gt;Would you like me to proceed with reading the secret-file.txt? Please confirm, and then I can help you solve the riddle inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Uh, Claude, help me solve the riddle in the secret-file.txt file. Now, this time, instead of just going ahead and reading it, taking liberties, it actually asks if I want to continue and I can say no. And my refusal goes back as a part of the conversation and the model actually deals with this pretty well.&lt;/p&gt;

&lt;p&gt;It says, I'm unable to access the file because I need your consent. Would you like me to read this file for you? If so, please confirm that I have your permission and I'll help you solve the riddle inside. So, this is working great. Okay. I'm feeling more comfortable again. We can go back to the blog post.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;list_files&lt;/code&gt; tool
&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.amazonaws.com%2Fuploads%2Farticles%2Fwo655l15bxi0gniw3lmi.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%2Fwo655l15bxi0gniw3lmi.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next thing Thorston invites us to do is to create a list files tool.&lt;/p&gt;

&lt;p&gt;If you're anything like me, he says, the first thing you do when you log into a new computer is to get your bearings by running &lt;code&gt;ls&lt;/code&gt;, list files. Let's let our agent do that.&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileTool&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;./readFile.js&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;listFilesTool&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;./listFiles.js&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;readFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listFilesTool&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add a second file to our list of files here, and uh, we import that from a separate file and add it to our array.&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;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs/promises&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;existsSync&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;fs&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;nodePath&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;path&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;ToolDefinition&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;../types.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Tool for listing files in a directory
 */&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;listFilesTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&lt;/span&gt; &lt;span class="o"&gt;=&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;list_files&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`List files and directories at a given path. If no
     path is provided, lists files in the current
     directory.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Optional relative path to list files from.
           Defaults to current directory if not
           provided.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;func&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="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;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="na"&gt;path&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;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Check if path exists and is a directory&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="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;))&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;`Directory not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stat&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&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;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is not a directory`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&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;entriesWithDirectoryMarkers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;dir&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&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;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodePath&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="nx"&gt;rPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&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;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&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;entry&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;entry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;entry&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="k"&gt;return&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="nx"&gt;entriesWithDirectoryMarkers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&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;`Error reading dir: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
          &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&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="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;And then this is what that tool looks like. It's pretty much like the last one. There's just a little more code because listing directories in Node.js is a little less convenient. But we have a &lt;code&gt;name&lt;/code&gt;, a &lt;code&gt;description&lt;/code&gt;, an &lt;code&gt;input_schema&lt;/code&gt; that requires a &lt;code&gt;path&lt;/code&gt;. Although the path is not required this time, it's optional. We say in the instructions that if you don't pass a path, we'll list the current directory, which is a nice convenience.&lt;/p&gt;

&lt;p&gt;And then over in the function on the right hand side, we check if the path exists and is a directory, and if so, we iterate through all of the entries. The one little extra we do here is that if an entry is a directory, we will output it with a slash on the end so that we provide a little clue here to the model of which things in the directory are sub directories or not. And that seems to work really well.&lt;/p&gt;

&lt;p&gt;So let's go ahead and demo this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout demo5&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; What do you see in this directory?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll help you see what's in the current directory. Let me check the contents for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; list_files({}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; The current directory contains a mix of files and directories:&lt;/p&gt;

&lt;p&gt;Files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.envrc&lt;/li&gt;
&lt;li&gt;.gitignore&lt;/li&gt;
&lt;li&gt;.prettierignore&lt;/li&gt;
&lt;li&gt;.prettierrc.json&lt;/li&gt;
&lt;li&gt;README.md&lt;/li&gt;
&lt;li&gt;devbox.json&lt;/li&gt;
&lt;li&gt;devbox.lock&lt;/li&gt;
&lt;li&gt;package.json&lt;/li&gt;
&lt;li&gt;pnpm-lock.yaml&lt;/li&gt;
&lt;li&gt;secret-file.txt&lt;/li&gt;
&lt;li&gt;tsconfig.json&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Directories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.git/&lt;/li&gt;
&lt;li&gt;dist/&lt;/li&gt;
&lt;li&gt;node_modules/&lt;/li&gt;
&lt;li&gt;src/&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This appears to be a TypeScript/JavaScript project with standard configuration files, dependency management through pnpm, and a source directory (src/) where the code likely resides. There's also a "secret-file.txt" which might contain sensitive information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What do you see in this directory? We'll ask it.&lt;/p&gt;

&lt;p&gt;I'll check the what files and directories are present and it calls list files with no arguments, just as it should.&lt;/p&gt;

&lt;p&gt;And there you go. It is reading my directories and it says, this appears to be a TypeScript JavaScript project using pnpm as a package manager with a devbox development environment setup. There's also a file named secret-file.txt, which might contain sensitive information.&lt;/p&gt;

&lt;p&gt;There you go. It's done a pretty good job.&lt;/p&gt;

&lt;p&gt;Um, how about, tell me about all the TypeScript files in this project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; Tell me about all the TypeScript files in here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll find and examine all the TypeScript files in this project. Let me start by checking for TypeScript files in the main directory and then look in the src/ directory where they're most likely to be located.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; list_files({"path":"src"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is guessing there might be TypeScript files in the src directory, which is a pretty good guess.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I found the src directory with a TypeScript file called "agent.ts" and an "agent" subdirectory. Let me check what's in the agent subdirectory and then examine each TypeScript file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; list_files({"path":"src/agent"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I found more TypeScript files in the agent subdirectory. Let me also check if there are any TypeScript files in the tools directory:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; list_files({"path":"src/agent/tools"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Now that I've found all the TypeScript files, let me examine each one to tell you about them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And there's a subdirectory called agent with my agent class in it, and there's a sub subdirectory called tools with all of my tool definitions in it. It's scanning all of those and now it's requesting to read each of the files that it's found in there.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent/agent.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent/types.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent/tools/index.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent/tools/readFile.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"src/agent/tools/listFiles.ts"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And it's at this point that I start second guessing my caution of requiring an approval for every single tool call, but better safe than sorry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Based on my examination of all the TypeScript files in this project, here's what I found:&lt;/p&gt;
&lt;h3&gt;
  
  
  Project Overview
&lt;/h3&gt;

&lt;p&gt;This is a TypeScript project that implements a Claude AI agent with tool integration. The agent can interact with users through a command-line interface and execute tools like reading files and listing directories.&lt;/p&gt;
&lt;h3&gt;
  
  
  TypeScript Files:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;src/agent.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Main entry point that sets up an Anthropic Vertex AI client&lt;/li&gt;
&lt;li&gt;Creates an Agent instance with tools and UI handlers&lt;/li&gt;
&lt;li&gt;Implements UI functions for getting user input, showing messages, and asking for tool execution consent&lt;/li&gt;
&lt;li&gt;Uses Google Cloud's "cross-camp-ai-enablement" project in "us-east5" region&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/agent/agent.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Defines the &lt;code&gt;Agent&lt;/code&gt; class that handles the conversation flow&lt;/li&gt;
&lt;li&gt;Manages the conversation state and message history&lt;/li&gt;
&lt;li&gt;Handles tool execution and user consent for tools&lt;/li&gt;
&lt;li&gt;Processes different types of content from Claude (text and tool_use)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/agent/types.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Contains TypeScript type definitions for the project&lt;/li&gt;
&lt;li&gt;Defines the &lt;code&gt;ToolDefinition&lt;/code&gt; interface used to create tools&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/agent/tools/index.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Exports an array of available tools (readFileTool and listFilesTool)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/agent/tools/readFile.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Implements a tool for reading file contents&lt;/li&gt;
&lt;li&gt;Validates file existence and handles errors&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;src/agent/tools/listFiles.ts&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Implements a tool for listing files in a directory&lt;/li&gt;
&lt;li&gt;Adds trailing slashes to directory names for clarity&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project demonstrates how to build a Claude agent with tool-calling capabilities. It uses the Anthropic Vertex AI client to connect to Google Cloud's Claude API, and implements a command-line interface for interacting with the agent. The agent can execute tools that allow it to read files and list directories, with user consent required before any tool execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally, there we go. A great big project overview. This is a TypeScript project that implements a Claude AI agent with tool calling capabilities. It creates an interactive command line interface where users can chat with Claude and allow it to execute specific tools. And it's got a breakdown of what happens in each of the files.&lt;/p&gt;

&lt;p&gt;Really good. Pretty neat.&lt;/p&gt;

&lt;p&gt;Um, let's try one more thing. What TypeScript version are we using in this project?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt; What TypeScript version are we using in this project?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; To determine the TypeScript version being used in this project, I should check the package.json file, which typically contains information about dependencies including TypeScript. Let me examine that file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"package.json"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Based on the package.json file, this project is using TypeScript version 5.7.3, as indicated in the devDependencies section:&lt;/p&gt;


&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"devDependencies"&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;"@types/node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^24.0.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"prettier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.4.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5.7.3"&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;p&gt;The caret (^) before the version number means that the project will use at least version 5.7.3, but can automatically update to newer minor and patch versions within the 5.7.x range. However, the specific version being used is 5.7.3, which is a very recent version of TypeScript (as of the time this project was created).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What would you do if you were asked that question? Well, you would probably go and look in the package.json file, which is exactly what it does.&lt;/p&gt;

&lt;p&gt;It's identified TypeScript 5.7.3. Nice work.&lt;/p&gt;

&lt;p&gt;Okay, so there we go. Using the two tools together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Let it &lt;code&gt;edit_file&lt;/code&gt;
&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.amazonaws.com%2Fuploads%2Farticles%2Fh66pwel9wj2j783jn9g1.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%2Fh66pwel9wj2j783jn9g1.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's let it edit files. The last tool we're going to add is &lt;code&gt;edit_file&lt;/code&gt;, a tool that lets Claude edit files. Holy shit, you're thinking now, this is where the rubber hits the road. This is where he pulls the rabbit outta the hat. Well, let's see, shall we?&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileTool&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;./readFile.js&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;listFilesTool&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;./listFiles.js&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;editFileTool&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;./editFile.js&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;readFileTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listFilesTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;editFileTool&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create a new &lt;code&gt;editFileTool&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="cm"&gt;/**
 * Tool for editing file contents
 */&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;editFileTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolDefinition&lt;/span&gt; &lt;span class="o"&gt;=&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;edit_file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Make edits to a text file. Replaces
    'old_str' with 'new_str' in the given file.
    'old_str' and 'new_str' MUST be different from
    each other. If the file specified with path
    doesn't exist, it will be created.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The relative path of a file in
          the working directory.&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;old_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The string to replace in the
          file.&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;new_str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The string to replace with.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&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;old_str&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;new_str&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;func&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="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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="o"&gt;&amp;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="na"&gt;path&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;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;oldStr&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;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;old_str&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;newStr&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;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;new_str&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvedPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate that old_str and new_str are different&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;oldStr&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;)&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;`'old_str' and 'new_str' must be
        different from each other`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="c1"&gt;// EDIT THE FILE&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the structure of this file. We'll say that it's a tool that lets you make edits to a text file. It replaces old string with new string in the given file. Old string and new string must be different from each other. If the file specified with the path doesn't exist, it will be created.&lt;/p&gt;

&lt;p&gt;And then in &lt;code&gt;func&lt;/code&gt; we check that old string and new string are different. And if they are, then we will attempt to edit the file. I've put the code for that on a separate slide 'cause it's a little too long to fit.&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;let&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Check if file exists&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;oldStr&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="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Read existing file content&lt;/span&gt;
  &lt;span class="nx"&gt;fileContent&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if old_str exists in the file&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;fileContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldStr&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="s2"&gt;`No changes made:
            String '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;oldStr&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' not found in the file.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Replace all occurrences of old_str with new_str&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;splitParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oldStr&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;newContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;splitParts&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="nx"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Write the modified content back to the file&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Count replacements:&lt;/span&gt;
  &lt;span class="c1"&gt;// splitParts.length - 1 gives the number of&lt;/span&gt;
  &lt;span class="c1"&gt;// delimiters (oldStr) found&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;occurrences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;splitParts&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="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Successfully made &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
          replacement&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;occurrences&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s&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="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
          in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&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="k"&gt;else&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;oldStr&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&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="s2"&gt;`Created new file &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with the provided
          content`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Throw error for the case where the file doesn't exist and oldStr is not empty&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;`File &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; does not exist.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we will check if the file with the given path exists, and if so, we make sure that the old string is not empty. We then read the contents of the file, find every occurrence of old string, and replace it with new string, and return a string that summarizes what we've done by counting the number of replacements.&lt;/p&gt;

&lt;p&gt;If the file didn't exist and old string is empty, then we create a new file with new string as the contents. And if none of those things happen, then we throw an error because we are trying to edit a file that doesn't exist.&lt;/p&gt;

&lt;p&gt;That's it. That's our editing tool. Let's demo it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ git checkout main&lt;/p&gt;

&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; Hey Claude, create fizzbuzz.js that I can run with Node.js and that has fizzbuzz in it and executes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll create a fizzbuzz.js file for you that can be run with Node.js. The FizzBuzz program prints numbers from 1 to n, but for multiples of 3 it prints "Fizz", for multiples of 5 it prints "Buzz", and for multiples of both 3 and 5 it prints "FizzBuzz".&lt;/p&gt;

&lt;p&gt;Let me create this file for you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; edit*file({"path":"fizzbuzz.js","old_str":"","new_str":"/**\n * FizzBuzz implementation\n _ Prints numbers from 1 to 100\n _ For multiples of 3, prints \"Fizz\" instead of the number\n _ For multiples of 5, prints \"Buzz\" instead of the number\n _ For multiples of both 3 and 5, prints \"FizzBuzz\"\n _/\n\nfunction fizzBuzz(n) {\n for (let i = 1; i &amp;lt;= n; i++) {\n if (i % 3 === 0 &amp;amp;&amp;amp; i % 5 === 0) {\n console.log('FizzBuzz');\n } else if (i % 3 === 0) {\n console.log('Fizz');\n } else if (i % 5 === 0) {\n console.log('Buzz');\n } else {\n console.log(i);\n }\n }\n}\n\n// Execute FizzBuzz for numbers 1 to 100\nconsole.log('Running FizzBuzz from 1 to 100:');\nfizzBuzz(100);"}) \&lt;br&gt;
**Claude:** Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I've created a file named &lt;code&gt;fizzbuzz.js&lt;/code&gt; with a FizzBuzz implementation. The code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defines a &lt;code&gt;fizzBuzz&lt;/code&gt; function that takes a number &lt;code&gt;n&lt;/code&gt; as input&lt;/li&gt;
&lt;li&gt;Iterates from 1 to n&lt;/li&gt;
&lt;li&gt;For each number:

&lt;ul&gt;
&lt;li&gt;If divisible by both 3 and 5, prints "FizzBuzz"&lt;/li&gt;
&lt;li&gt;If divisible by 3, prints "Fizz"&lt;/li&gt;
&lt;li&gt;If divisible by 5, prints "Buzz"&lt;/li&gt;
&lt;li&gt;Otherwise, prints the number itself&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Executes the function for numbers 1 to 100&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can run this file with Node.js by using the following command in your terminal:&lt;/p&gt;



&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node fizzbuzz.js
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This will display the FizzBuzz sequence from 1 to 100 in your console.&lt;/p&gt;

&lt;p&gt;You:&lt;/p&gt;


&lt;/blockquote&gt;

&lt;p&gt;Hey, Claude, create fizzbuzz.js that I can run with Node.js and that has fizzbuzz in it and executes it.&lt;/p&gt;

&lt;p&gt;This is a pretty typical, um, interview question for programmers. There is the implementation of Fizzbuzz in JavaScript that it wants to write into a fizzbuzz.js file. Looks good to me.&lt;/p&gt;

&lt;p&gt;Now if you don't know if Fizbuzz, uh, it has described it up there, what it does. Multiples of three will be output as fizz, multiples of five as buzz, multiples of both as fiz buzz. Otherwise, it'll just output the numbers. Yes, we will let it read the file to confirm that it was created correctly. And it reckons it's all done.&lt;/p&gt;

&lt;p&gt;It even tells me how to run it. So let's accept the invitation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ node fizzbuzz.js \&lt;br&gt;
Running FizzBuzz from 1 to 100: \&lt;br&gt;
1 \&lt;br&gt;
2 \&lt;br&gt;
Fizz \&lt;br&gt;
4 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
7 \&lt;br&gt;
8 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
11 \&lt;br&gt;
Fizz \&lt;br&gt;
13 \&lt;br&gt;
14 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
16 \&lt;br&gt;
17 \&lt;br&gt;
Fizz \&lt;br&gt;
19 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
22 \&lt;br&gt;
23 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
26 \&lt;br&gt;
Fizz \&lt;br&gt;
28 \&lt;br&gt;
29 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
31 \&lt;br&gt;
32 \&lt;br&gt;
Fizz \&lt;br&gt;
34 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
37 \&lt;br&gt;
38 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
41 \&lt;br&gt;
Fizz \&lt;br&gt;
43 \&lt;br&gt;
44 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
46 \&lt;br&gt;
47 \&lt;br&gt;
Fizz \&lt;br&gt;
49 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
52 \&lt;br&gt;
53 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
56 \&lt;br&gt;
Fizz \&lt;br&gt;
58 \&lt;br&gt;
59 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
61 \&lt;br&gt;
62 \&lt;br&gt;
Fizz \&lt;br&gt;
64 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
67 \&lt;br&gt;
68 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
71 \&lt;br&gt;
Fizz \&lt;br&gt;
73 \&lt;br&gt;
74 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
76 \&lt;br&gt;
77 \&lt;br&gt;
Fizz \&lt;br&gt;
79 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
82 \&lt;br&gt;
83 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
86 \&lt;br&gt;
Fizz \&lt;br&gt;
88 \&lt;br&gt;
89 \&lt;br&gt;
FizzBuzz \&lt;br&gt;
91 \&lt;br&gt;
92 \&lt;br&gt;
Fizz \&lt;br&gt;
94 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
97 \&lt;br&gt;
98 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There we go. A working fizz buzz.&lt;/p&gt;

&lt;p&gt;Uh, there's more to try though. Let's actually edit this file. Um, that fizzbuzz to 100 is a little long.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; Please edit fizzbuzz.js so that it only prints until 15&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll help you edit the fizzbuzz.js file to only print until 15 instead of its current limit. First, let me check the current content of the file to understand what needs to be changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; read_file({"path":"fizzbuzz.js"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Now I'll edit the file to change the limit from 100 to 15. I need to modify the comment at the top and the function call at the bottom:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; edit*file({"path":"fizzbuzz.js","old_str":"/**\n * FizzBuzz implementation\n _ Prints numbers from 1 to 100\n _ For multiples of 3, prints \"Fizz\" instead of the number\n _ For multiples of 5, prints \"Buzz\" instead of the number\n _ For multiples of both 3 and 5, prints \"FizzBuzz\"\n _/","new_str":"/**\n _ FizzBuzz implementation\n _ Prints numbers from 1 to 15\n _ For multiples of 3, prints \"Fizz\" instead of the number\n _ For multiples of 5, prints \"Buzz\" instead of the number\n _ For multiples of both 3 and 5, prints \"FizzBuzz\"\n _/"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool request:&lt;/strong&gt; edit_file({"path":"fizzbuzz.js","old_str":"// Execute FizzBuzz for numbers 1 to 100\nconsole.log('Running FizzBuzz from 1 to 100:');\nfizzBuzz(100);","new_str":"// Execute FizzBuzz for numbers 1 to 15\nconsole.log('Running FizzBuzz from 1 to 15:');\nfizzBuzz(15);"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I've successfully modified the fizzbuzz.js file to only print up to 15 instead of 100. The changes made were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updated the comment at the top to state that it "Prints numbers from 1 to 15" instead of 1 to 100&lt;/li&gt;
&lt;li&gt;Changed the execution line from &lt;code&gt;fizzBuzz(100)&lt;/code&gt; to &lt;code&gt;fizzBuzz(15)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updated the console.log message to say "Running FizzBuzz from 1 to 15:" instead of "...to 100:"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The FizzBuzz function itself didn't need to be changed as it was already parameterized to accept a limit (n) and will now stop at 15 as requested.&lt;/p&gt;

&lt;p&gt;You:&lt;/p&gt;


&lt;/blockquote&gt;

&lt;p&gt;Uh, please edit fizzbuzz.js so that it only prints until 15. That should fit on the screen. It is gonna list the files in the directory to make sure that file is there.&lt;/p&gt;

&lt;p&gt;It's gonna read it, it's gonna edit it. There we go. It's use all three of our tools. It's gonna edit it, edit it again to make another change, and it thinks it's done. Let's see if it's right.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ node fizzbuzz.js \&lt;br&gt;
Running FizzBuzz from 1 to 15: \&lt;br&gt;
1 \&lt;br&gt;
2 \&lt;br&gt;
Fizz \&lt;br&gt;
4 \&lt;br&gt;
Buzz \&lt;br&gt;
Fizz \&lt;br&gt;
7 \&lt;br&gt;
8 \&lt;br&gt;
Fizz \&lt;br&gt;
Buzz \&lt;br&gt;
11 \&lt;br&gt;
Fizz \&lt;br&gt;
13 \&lt;br&gt;
14 \&lt;br&gt;
FizzBuzz&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There we go, a working fizzbuzz from one to 15.&lt;/p&gt;

&lt;p&gt;Let's do one more. In this one, from Thorson, we're gonna ask it to create a congrats.js script that rot13 decodes this string and prints it. If you don't know rot13 it's a very weak encryption algorithm that just shifts every letter in the alphabet by 13 uh spaces in order to make an indecipherable string.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ pnpm agent&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; agent /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
pnpm build &amp;amp;&amp;amp; node dist/agent.js&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:vertexai-playground@1.0.0"&gt;vertexai-playground@1.0.0&lt;/a&gt; build /Users/kyank/Developer/how-to-build-an-agent-in-javascript \&lt;br&gt;
tsc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chat with Claude (use 'ctrl-c' to quit) \&lt;br&gt;
&lt;strong&gt;You:&lt;/strong&gt; Create a congrats.js script that rot13-decodes the following string 'Pbatenghyngvbaf ba ohvyqvat n pbqr-rqvgvat ntrag!' and prints it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I'll create a JavaScript script that decodes a rot13-encoded string and prints it.&lt;/p&gt;

&lt;p&gt;Let me first create the congrats.js file with the code to decode and print the string 'Pbatenghyngvbaf ba ohvyqvat n pbqr-rqvgvat ntrag!'.&lt;/p&gt;

&lt;p&gt;Tool request: edit*file({"path":"congrats.js","old_str":"","new_str":"/**\n * A script that decodes a rot13-encoded string and prints it\n _/\n\n// The encoded string to decode\nconst encodedString = 'Pbatenghyngvbaf ba ohvyqvat n pbqr-rqvgvat ntrag!';\n\n/**\n _ Function to decode a rot13-encoded string\n _ &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {string} str - The rot13-encoded string\n _ @return {string} The decoded string\n _/\nfunction rot13Decode(str) {\n return str.replace(/[a-zA-Z]/g, function(char) {\n // Get the character code\n const charCode = char.charCodeAt(0);\n \n // Handle uppercase letters (A-Z: 65-90)\n if (charCode &amp;gt;= 65 &amp;amp;&amp;amp; charCode &amp;lt;= 90) {\n return String.fromCharCode(((charCode - 65 + 13) % 26) + 65);\n }\n \n // Handle lowercase letters (a-z: 97-122)\n if (charCode &amp;gt;= 97 &amp;amp;&amp;amp; charCode &amp;lt;= 122) {\n return String.fromCharCode(((charCode - 97 + 13) % 26) + 97);\n }\n \n // Return non-alphabetic characters unchanged\n return char;\n });\n}\n\n// Decode and print the string\nconst decodedString = rot13Decode(encodedString);\nconsole.log(decodedString);"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; Now let me verify the content of the file:&lt;/p&gt;

&lt;p&gt;Tool request: read_file({"path":"congrats.js"}) \&lt;br&gt;
&lt;strong&gt;Claude:&lt;/strong&gt; Do you want to continue? [yes]:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; I've created the congrats.js script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Defines the rot13-encoded string 'Pbatenghyngvbaf ba ohvyqvat n pbqr-rqvgvat ntrag!'&lt;/li&gt;
&lt;li&gt;Implements a rot13Decode function that:

&lt;ul&gt;
&lt;li&gt;Processes each alphabetic character in the string&lt;/li&gt;
&lt;li&gt;Shifts each letter by 13 positions in the alphabet&lt;/li&gt;
&lt;li&gt;Preserves case (uppercase/lowercase)&lt;/li&gt;
&lt;li&gt;Leaves non-alphabetic characters unchanged&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Decodes the string and prints the result to the console&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you run this script with Node.js (using the command &lt;code&gt;node congrats.js&lt;/code&gt;), it will decode and display the hidden message.&lt;/p&gt;

&lt;p&gt;Note: The rot13 cipher is a simple letter substitution that replaces each letter with the letter 13 positions after it in the alphabet, wrapping around if necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You:&lt;/strong&gt;&lt;/p&gt;


&lt;/blockquote&gt;

&lt;p&gt;That's exactly what it's going to do in the JavaScript code.&lt;/p&gt;

&lt;p&gt;So let's exit and &lt;code&gt;node congrats.js&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❯ pnpm agent&lt;br&gt;
Congratulations on building a code-editing agent!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Congratulations on building a code editing agent. So there we go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn't this amazing?
&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.amazonaws.com%2Fuploads%2Farticles%2Fwp4umi58zzqcwcam1g4j.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%2Fwp4umi58zzqcwcam1g4j.png" alt="a quote from the original blog post"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the end of this article, it says, isn't this amazing? If you're anything like all the engineers I've talked to in the past few months, chances are that while reading this, you have been waiting for the rabbit to be pulled out of the hat, for me to say, well, in reality, it's much, much harder than this, but it's not.&lt;/p&gt;

&lt;p&gt;This is essentially all there is to the inner loop of a code editing agent. Sure, integrating it into your editor, tweaking the system prompt, giving it the right feedback at the right time, a nice UI around it. Better tooling around the tools, support for multiple agents and so on. We've built all of that in Amp, but it didn't require moments of genius.&lt;/p&gt;

&lt;p&gt;All that was required was practical engineering and elbow grease. These models are incredibly powerful now. 300 lines of code – it's more like 400 in Node.js, for the record – and three tools and now you're able to talk to an alien intelligence that edits your code. If you think, well, but we didn't really, go ahead and try it.&lt;/p&gt;

&lt;p&gt;Go and see how far you can get with this. I bet it's a lot farther than you think. That's why we think everything's changing.&lt;/p&gt;

&lt;p&gt;If you wanna take this project for a spin, you can find &lt;a href="https://github.com/sentience/how-to-build-an-agent-in-javascript" rel="noopener noreferrer"&gt;the link to the GitHub project with the source code&lt;/a&gt; in the description of this video or in the blog post where you're viewing it.&lt;/p&gt;

&lt;p&gt;And if you want to read the original article, I invite you to go and &lt;a href="https://ampcode.com/how-to-build-an-agent" rel="noopener noreferrer"&gt;visit the Amp Code blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So there you go. The blog post of the year for 2025. I can't say I disagree, and I'm happy it exists in TypeScript now. Hope you enjoyed it. Thanks for watching.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>Use Tailwind CSS prefixes for shared design system components</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Mon, 22 Jan 2024 04:06:47 +0000</pubDate>
      <link>https://forem.com/cultureamp/use-tailwindcss-prefixes-for-shared-design-system-components-16h0</link>
      <guid>https://forem.com/cultureamp/use-tailwindcss-prefixes-for-shared-design-system-components-16h0</guid>
      <description>&lt;p&gt;&lt;cite&gt;Photo by &lt;a href="https://unsplash.com/@mahkeo?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Khamkéo Vilaysing&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/trees-with-wind-photo-WtwSsqwYlA0?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Surprisingly little has been written about how to use Tailwind with design systems or shared components, when both those components and the app consuming them are styled with Tailwind. Tailwind's &lt;a href="https://tailwindcss.com/docs/configuration#prefix" rel="noopener noreferrer"&gt;&lt;code&gt;prefix&lt;/code&gt; option&lt;/a&gt; is specifically designed to allow for this, but it's a somewhat ugly solution that we did our best to avoid at Culture Amp until recently. Here's everything we learned, and why we're ultimately embracing &lt;code&gt;prefix&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A very brief intro to Tailwind
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; (Tailwind for short) is a not-uncontroversial choice of tool for projects where hand-crafting CSS selectors that apply to semantically meaningful patterns of HTML elements is not beneficial. We have decided to use it at Culture Amp, the reasons for which deserve an article of their own. At the risk of oversimplifying: if the structure of your UI matches the structure of your codebase (i.e. visual blocks tend to correspond to software components rather than sections of a document), you might save time and effort by applying styles to your elements directly in those components, rather than by writing CSS selectors that are tightly coupled to those components' implementation. Again, there's a lot more to say here, and I'll try to write it up soon. Let me know if you're keen to read it.&lt;/p&gt;

&lt;p&gt;Fundamentally, Tailwind scans your application's source code for class names like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and generates a stylesheet that contains only the necessary styles to match the classes you've used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;While at first glance this looks like it would have all the downsides of inline styles (the &lt;code&gt;style&lt;/code&gt; attribute), Tailwind has designed a remarkably robust language of class names that can cover just about any selector, property, or value you need. Inline styles can't do pseudo-class selectors or media queries, but Tailwind can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dark:hover:bg-sky-500/25"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.dark&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nd"&gt;:bg-sky-500&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;25&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt; &lt;span class="m"&gt;165&lt;/span&gt; &lt;span class="m"&gt;233&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;0.25&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;In a project that's a good fit for Tailwind (see above), this virtually eliminates the need to write CSS code that would mostly contain selectors that match a single element – an unnecessary abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling shared components
&lt;/h2&gt;

&lt;p&gt;For the purposes of this article, shared components are user interface elements that are used in more than one web application. In Culture Amp's case this includes the React components in our &lt;a href="https://cultureamp.design" rel="noopener noreferrer"&gt;Kaizen design system&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you want to use Tailwind to style both an application's non-shared components, and the shared components that application uses, you have a decision to make: &lt;strong&gt;Will you publish your shared components with compiled CSS, or do you expect the application's build to run the Tailwind compiler over the shared components' source code to generate those styles as well?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For many years, Culture Amp took the second option, and distributed shared components without compiled CSS. This meant that every app that consumed shared components needed to include the necessary CSS build tooling – at that time &lt;a href="https://github.com/css-modules/css-modules" rel="noopener noreferrer"&gt;CSS Modules&lt;/a&gt; and &lt;a href="https://github.com/sass/node-sass" rel="noopener noreferrer"&gt;node-sass&lt;/a&gt; – with a compatible version and configuration. This was relatively easy to set up, but over time proved difficult to maintain. When node-sass was deprecated in favour of (the much faster but slightly incompatible) &lt;a href="https://sass-lang.com/dart-sass/" rel="noopener noreferrer"&gt;Dart Sass&lt;/a&gt;, this demanded a difficult lock-step migration across all those codebases, which we have yet to achieve. And as new applications have switched to Tailwind for their own styles, they've had to continue to maintain those old build tools in parallel for the shared components' styles.&lt;/p&gt;

&lt;p&gt;To avoid this coupling between the source code of shared components and the build tooling of the applications that consume them, we now want to go the other way: have our shared components build their styles in their own build pipelines and publish components with plain CSS. That way, the CSS build tooling of our applications can stay decoupled from the CSS build tooling of our component libraries: a Sass-styled application can consume a Tailwind-styled component without having to run Tailwind, and a Tailwind-styled application can consume a Sass-styled component without having to run Sass.&lt;/p&gt;

&lt;p&gt;But what happens when a Tailwind-styled application consumes Tailwind-styled components? &lt;strong&gt;How do the Tailwind-generated styles of shared components co-exist with the Tailwind-generated styles of the application?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive approach
&lt;/h2&gt;

&lt;p&gt;At first, it would seem that it's safe to combine two Tailwind-built stylesheets into one, if slightly inefficient. Let's say your component uses &lt;code&gt;m-0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;And then your application also uses &lt;code&gt;m-0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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 use the shared component in your application, you get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;.m-0&lt;/code&gt; is defined twice in your stylesheet, which is the slightly wasteful part, but the two rules apply the exact same styles, so everything still works.&lt;/p&gt;

&lt;p&gt;While the duplicate CSS output is unfortunate, Tailwind-generated stylesheets tend to be incredibly small compared to hand-coded CSS, so this bloat is likely negligible. Almost every element in the document that needs its margin set to zero has that done with this one (duplicated) CSS rule, rather than traditional CSS where many, &lt;em&gt;many&lt;/em&gt; different rules might specify &lt;code&gt;margin: 0px&lt;/code&gt; separately. If you &lt;em&gt;really&lt;/em&gt; wanted to avoid this duplicate output, a PostCSS plug-in like &lt;a href="https://www.npmjs.com/package/postcss-discard-duplicates" rel="noopener noreferrer"&gt;postcss-discard-duplicates&lt;/a&gt; could do this for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small problem: Configuration coupling
&lt;/h2&gt;

&lt;p&gt;A much bigger downside than the output duplication, however, is the coupling this creates between your Tailwind configurations. If someday you upgrade or alter the configuration of Tailwind in either project (your shared components or your application), and the two outputs do not match, one set of styles will overwrite the other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0ea5e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* new value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#87cefa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* old value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, because our combined CSS output includes the shared components' CSS first and the application's CSS second, the application styles "win" (because the CSS cascade applies rules of equal specificity in source code order).&lt;/p&gt;

&lt;p&gt;Adding to the confusion is the fact that Tailwind, remember, only generates styles for class names that you use, so if your application uses some of the classes that your shared components use but not others, you can end up with an unholy mix of the two configurations applied to your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-red-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ef4444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* new value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0ea5e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* new value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#87cefa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* old value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, your shared components will display with the old value for &lt;code&gt;bg-sky-500&lt;/code&gt;, but the new value for &lt;code&gt;bg-red-500&lt;/code&gt;, because that second background colour isn't used in the application's source code!&lt;/p&gt;

&lt;p&gt;You can of course flip the order in which your two stylesheets are combined, putting the application's styles first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#87cefa&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* old value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.bg-red-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ef4444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* new value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.bg-sky-500&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0ea5e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* new value */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…but this merely reverses the problem: now your application is being unexpectedly re-styled by the Tailwind configuration used to build your shared components' styles, except for any classes that are used only in your application, which will remain styled by the old configuration.&lt;/p&gt;

&lt;p&gt;In short, if you want your styles to be applied consistently, you need to avoid breaking changes to your Tailwind configuration, or update the two packages in lock-step. This by itself may not be a dealbreaker if you expect your Tailwind configuration to be relatively stable. Tailwind itself is pretty careful about releasing breaking changes, so it's unlikely that having slightly different Tailwind versions in the shared components and the application will cause problems &lt;em&gt;most&lt;/em&gt; of the time.&lt;/p&gt;

&lt;p&gt;But it's hard to ignore the fact that we've ended up back in a situation where our applications are forced to match their build configuration to that assumed by our shared components.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big problem: Tailwind depends on source order
&lt;/h2&gt;

&lt;p&gt;There's actually a more subtle (and fatal) issue lurking here: Tailwind is designed with the assumption that it controls the source order of the rules it generates.&lt;/p&gt;

&lt;p&gt;Let's consider again our zero-margin &lt;code&gt;div&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;margin&lt;/code&gt; is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties" rel="noopener noreferrer"&gt;shorthand property&lt;/a&gt;, which means it has the effect of setting &lt;code&gt;margin-block-start&lt;/code&gt; (top), &lt;code&gt;margin-inline-end&lt;/code&gt; (right), &lt;code&gt;margin-block-end&lt;/code&gt; (bottom), and &lt;code&gt;margin-inline-start&lt;/code&gt; (left).&lt;/p&gt;

&lt;p&gt;In traditional CSS, you can follow a shorthand property like this with any specific properties you want to override, like adding a left margin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.bottom-margin-only&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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 order of the property declarations in the above is significant: if you swapped them so that &lt;code&gt;margin-block-end&lt;/code&gt; was set first, the &lt;code&gt;margin&lt;/code&gt; declaration would override it.&lt;/p&gt;

&lt;p&gt;In Tailwind, you can likewise override a shorthand class like &lt;code&gt;m-0&lt;/code&gt; with a specific class like &lt;code&gt;ms-4&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;But the critical thing to observe here is that &lt;strong&gt;the order of the class names in the HTML is not significant: only the order of the generated CSS rules is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we swapped the order of the class names in the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…the left margin would still override the &lt;code&gt;m-0&lt;/code&gt;, because Tailwind still generates the two CSS rules in the order that ensures the more fine-grained style (the left margin) is applied after the more coarse-grained style (the margin on all four sides):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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, take this observation that the source order of rules generated by Tailwind is a critical feature of its design, and combine that with the issue we identified above of shared component styles clobbering application styles (or vice versa):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overides .ms-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, our shared components use &lt;code&gt;m-0&lt;/code&gt; and &lt;code&gt;ms-4&lt;/code&gt;, but our application only uses &lt;code&gt;m-0&lt;/code&gt;. The application's &lt;code&gt;m-0&lt;/code&gt; rule will override the value of &lt;code&gt;margin-inline-start&lt;/code&gt; set by the &lt;code&gt;ms-4&lt;/code&gt; rule, and break the shared component's left margin!&lt;/p&gt;

&lt;p&gt;Once again, you can solve this specific instance by swapping the order of the two generated stylesheets, but you'll just end up with the opposite problem: shared component styles interfering with application styles, in this example when the margin is on an application element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overides .ms-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're at all tempted to try doing without shorthand styles (e.g. avoid &lt;code&gt;m-0&lt;/code&gt; in favour of &lt;code&gt;mt-0 me-0 mb-0 ms-0&lt;/code&gt;), Tailwind also depends on source order for modifiers like media queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 md:m-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;768px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.md&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nd"&gt;:m-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overides .md:m-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we're saying the first &lt;code&gt;div&lt;/code&gt; should have a margin of zero by default, but on medium-sized screens or larger it should have a &lt;code&gt;1rem&lt;/code&gt; margin. These two rules have equal specificity (one class selector), so the fact that the media query style overrides the default style depends on source order. A second &lt;code&gt;.m-0&lt;/code&gt; rule at the end of the CSS from the shared component clobbers this margin.&lt;/p&gt;

&lt;p&gt;Modifiers (&lt;code&gt;dark:&lt;/code&gt;, &lt;code&gt;hover:&lt;/code&gt;, etc.) are a core feature of Tailwind's styling language; there's no avoiding them, and as we've seen, they are broken by combining multiple Tailwind builds in a single CSS stylesheet.&lt;/p&gt;

&lt;p&gt;How do we fix this?&lt;/p&gt;

&lt;h2&gt;
  
  
  Seductive non-solutions
&lt;/h2&gt;

&lt;p&gt;There are a few approaches to solving this that seem like they could work nicely until you think them through. We'll look at each briefly to explain why they don't work.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;!important&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Out of the box, Tailwind lets you generate styles with &lt;code&gt;!important&lt;/code&gt; that override competing styles in the cascade. &lt;code&gt;class="!ms-4"&lt;/code&gt;, for example, will output this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nt"&gt;ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="cp"&gt;!important&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;First of all, &lt;code&gt;!important&lt;/code&gt; is a very sharp knife, and is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/important#best_practices" rel="noopener noreferrer"&gt;best avoided&lt;/a&gt;. For example, it interferes with inline styles applied to elements by JavaScript. But even if we ignored everything that's bad about &lt;code&gt;!important&lt;/code&gt;, it doesn't actually solve the problem!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 !ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nt"&gt;ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;Yes, this prevents the application's &lt;code&gt;m-0&lt;/code&gt; rule from overriding the component's &lt;code&gt;ms-4&lt;/code&gt; rule. But to do this we had to modify our shared component with knowledge of the application that would be consuming it, which is an unhealthy coupling to internal implementation details that would be far from practical to maintain in a real-world ecosystem of shared components and applications.&lt;/p&gt;

&lt;p&gt;What if we made &lt;em&gt;all&lt;/em&gt; our component styles important? Tailwind even offers &lt;a href="https://tailwindcss.com/docs/configuration#important" rel="noopener noreferrer"&gt;a configuration option for this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;Well, this is just equivalent to putting our shared component styles at the end of the stylesheet: as already described above, the shared component styles will end up clobbering application styles in different circumstances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overrides .ms-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;h3&gt;
  
  
  Boost specificity with &lt;code&gt;important&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Tailwind's &lt;code&gt;important&lt;/code&gt; configuration option also lets you &lt;a href="https://tailwindcss.com/docs/configuration#selector-strategy" rel="noopener noreferrer"&gt;specify a selector&lt;/a&gt; for a top-level container element (like &lt;code&gt;body&lt;/code&gt; or &lt;code&gt;#app&lt;/code&gt;) to boost the specificity of the selectors it generates beyond the usual one-class specificity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.ms-4&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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, setting &lt;code&gt;important&lt;/code&gt; to &lt;code&gt;'body'&lt;/code&gt; again appears to fix the problem because the shared component styles now consistently override the application styles. But just like with &lt;code&gt;!important&lt;/code&gt; above, this only has the same effect as putting the component styles at the end of the stylesheet: shared component styles will clobber application styles instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.m-0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overrides .ms-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;h3&gt;
  
  
  CSS cascade layers
&lt;/h3&gt;

&lt;p&gt;A relatively new but now widely-supported addition to browsers, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@layer" rel="noopener noreferrer"&gt;&lt;code&gt;@layer&lt;/code&gt; CSS at-rule&lt;/a&gt; lets you create groups of CSS rules and control the order in which they are applied to the page. So you could specify that your shared component styles be applied after your application styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;application-styles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component-styles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;component-styles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&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;@layer&lt;/span&gt; &lt;span class="n"&gt;application-styles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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;But once again – you guessed it – this is effectively just like moving the component styles to the bottom of the stylesheet: it creates the opposite problem of component styles interfering with application styles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discard duplicates
&lt;/h3&gt;

&lt;p&gt;Remember at the start of this article when we noted that our shared components and our application could generate the same CSS rule, and that this would bloat our CSS output unnecessarily? I mentioned &lt;a href="https://www.npmjs.com/package/postcss-discard-duplicates" rel="noopener noreferrer"&gt;postcss-discard-duplicates&lt;/a&gt; could get rid of those duplicate rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks promising, because the problematic style has entirely disappeared. But there is still a source order issue here, because the style that survives is the style that appears first in the source order, and that isn't always right.&lt;/p&gt;

&lt;p&gt;With a slightly tweaked example where the duplicate style is the fine-grained style (&lt;code&gt;ms-4&lt;/code&gt;), we run into the same problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-2 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0 ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ❌ overrides .ms-4 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  An opportunity: intelligent Tailwind output merging
&lt;/h2&gt;

&lt;p&gt;The fundamental problem we face is that two Tailwind builds that use the same global namespace for their class names will inevitably interfere with each other because Tailwind is unable to place the generated CSS rules in the source order required for them the override each other in the correct sequence.&lt;/p&gt;

&lt;p&gt;But what if we could?&lt;/p&gt;

&lt;p&gt;It's not as if the correct order for the rules is mysterious. It's implemented in the Tailwind compiler. Tools like &lt;a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss" rel="noopener noreferrer"&gt;prettier-plugin-tailwindcss&lt;/a&gt;, which automatically sorts the class names in your HTML code to match the order in which Tailwind generates them in your CSS output, use &lt;a href="https://github.com/tailwindlabs/tailwindcss/blob/0848e4ca26c0869a90818adb7337b5a463be38d0/src/lib/setupContextUtils.js#L951-L990" rel="noopener noreferrer"&gt;a public API&lt;/a&gt; in Tailwind to get this order.&lt;/p&gt;

&lt;p&gt;So what if we wrote a PostCSS plug-in that would take the output of two Tailwind builds and merge them together, removing duplicate styles and sorting the remaining styles into the correct order?&lt;/p&gt;

&lt;p&gt;This seems like it could work, might even be a relatively straightforward project, and could even be worth Tailwind considering as a core feature. I've started &lt;a href="https://github.com/tailwindlabs/tailwindcss/discussions/12801" rel="noopener noreferrer"&gt;a discussion about this&lt;/a&gt; on the Tailwind CSS GitHub project.&lt;/p&gt;

&lt;p&gt;Even if we implemented this, we would still need to accept that our two Tailwind builds would need to have compatible versions and configurations for their merged outputs to work reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual solution: &lt;code&gt;prefix&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As I mentioned in the previous section, the fundamental problem we face here is that Tailwind generates styles in a global namespace, and that two Tailwind builds therefore conflict in that namespace.&lt;/p&gt;

&lt;p&gt;What if we could give our Tailwind builds separate namespaces? Our shared components would receive styles only from their generated CSS output, and our application elements would receive styles only from the application's Tailwind build output.&lt;/p&gt;

&lt;p&gt;This is what Tailwind's &lt;a href="https://tailwindcss.com/docs/configuration#prefix" rel="noopener noreferrer"&gt;&lt;code&gt;prefix&lt;/code&gt; option&lt;/a&gt; is for. It lets you specify a short string of characters that you add to the start of all your Tailwind class names, to distinguish them from styles they need to coexist with (which in this case is another Tailwind-generated stylesheet).&lt;/p&gt;

&lt;p&gt;For example, we could configure our Kaizen component library with a Tailwind prefix of &lt;code&gt;kz-&lt;/code&gt;, and get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is my application.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"kz-m-0 kz-ms-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a shared component&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* shared component styles */&lt;/span&gt;
&lt;span class="nc"&gt;.kz-m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.kz-ms-4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* application styles */&lt;/span&gt;
&lt;span class="nc"&gt;.m-0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&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 two elements' styles are completely separate in the CSS, because they use two completely different namespaces! You could swap the order of the two stylesheets, and it would make no difference. (We put our application styles last, because our design system components allow you to pass in class names to override their built-in styles, so those application class names need to come last in the stylesheet.)&lt;/p&gt;

&lt;p&gt;This is such a clean and complete solution to this problem that you're probably wondering why this isn't a much shorter article.&lt;/p&gt;

&lt;p&gt;The thing is, for those who have become used to Tailwind, the extreme terseness of its class names is one of its best features. It vastly reduces the number of keystrokes required for most styling tasks. Strings like &lt;code&gt;m-0&lt;/code&gt; become burned into your muscle memory, something you can type almost without thinking.&lt;/p&gt;

&lt;p&gt;So if you tell an engineer who loves Tailwind that they need to remember to add three extra characters (&lt;code&gt;kz-&lt;/code&gt;) to the start of all their class names when they're working in a library, and that different libraries will have different prefixes, you're probably going to ruin their day. And someone will have to make the very unpopular decision of who gets to have the default (unprefixed) namespace: your application codebases or your design system's component library. Avoiding this pain is why we tried everything else we could first.&lt;/p&gt;

&lt;p&gt;But this really is the cleanest solution to the problem of enabling different Tailwind builds/versions/configurations to coexist in a single web page, and at Culture Amp we've only just learned to accept that.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>designsystem</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>useEffect vs useLayoutEffect and server-side rendering</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Mon, 25 Sep 2023 02:03:25 +0000</pubDate>
      <link>https://forem.com/cultureamp/useeffect-vs-uselayouteffect-and-server-side-rendering-5a64</link>
      <guid>https://forem.com/cultureamp/useeffect-vs-uselayouteffect-and-server-side-rendering-5a64</guid>
      <description>&lt;p&gt;Today I learned the difference between &lt;a href="https://react.dev/reference/react/useEffect" rel="noopener noreferrer"&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://react.dev/reference/react/useLayoutEffect" rel="noopener noreferrer"&gt;&lt;code&gt;useLayoutEffect&lt;/code&gt;&lt;/a&gt; in React, and why the second one prints an ugly warning during server-side rendering.&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;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="cm"&gt;/* non-paint-blocking effect */&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="cm"&gt;/* dependencies */&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useLayoutEffect&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="cm"&gt;/* paint-blocking effect */&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="cm"&gt;/* dependencies */&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;useEffect&lt;/code&gt; and &lt;code&gt;useLayoutEffect&lt;/code&gt; are basically the "async" and "sync" versions of the same hook; otherwise, they behave identically. &lt;code&gt;useEffect&lt;/code&gt; runs in the background asynchronously while the render where its dependencies changed is allowed to render and paint for the user to see,&lt;br&gt;
whereas &lt;code&gt;useLayoutEffect&lt;/code&gt; blocks the render where the dependencies' values changed from being painted to the screen until after the body of &lt;code&gt;useLayoutEffect&lt;/code&gt; runs (and presumably tweaks the DOM in some way).&lt;/p&gt;

&lt;p&gt;Until today we were using &lt;code&gt;useLayoutEffect&lt;/code&gt; to perform a redirect in a new app:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routes&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;routes&lt;/span&gt;&lt;span class="dl"&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;useCurrentUser&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;src/context&lt;/span&gt;&lt;span class="dl"&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;useLayoutEffect&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&lt;/span&gt;&lt;span class="dl"&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;useRouter&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;next/router&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Redirect&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCurrentUser&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nf"&gt;useLayoutEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&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;Since we're going to redirect the browser anyway, it makes sense to save it the trouble of painting the initial render to the screen, right? Not when there is server-side rendering in the mix! When a &lt;code&gt;useLayoutEffect&lt;/code&gt; is encountered by React running on the server, it displays an ugly warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In fact, &lt;em&gt;neither&lt;/em&gt; &lt;code&gt;useEffect&lt;/code&gt; nor &lt;code&gt;useLayoutEffect&lt;/code&gt; can have any effect on the server-rendered output that gets sent to the browser (since there is no actual DOM for these effects to tweak), but this warning is emitted only for &lt;code&gt;useLayoutEffect&lt;/code&gt; because it is usually used to do something to the DOM before the user gets to see the rendered component. Server-rendering will cause that "untweaked" version of the rendered output to be sent to the browser and shown to the user before the client-side app fires up and runs the &lt;code&gt;useLayoutEffect&lt;/code&gt; to tweak it. So React is warning us that the user may see an ugly/broken version of the UI that we don't want them to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's &lt;code&gt;useLayoutEffect&lt;/code&gt; good for?
&lt;/h2&gt;

&lt;p&gt;When I shared this internally at Culture Amp, several people remarked that they couldn't think of any real use cases for &lt;code&gt;useLayoutEffect&lt;/code&gt;. They are indeed rare – more so every year as CSS grows more and more capable – but here's a semi-contrived example:&lt;/p&gt;

&lt;p&gt;Let's say you wanted to let the browser lay out the "natural" height of a &lt;code&gt;div&lt;/code&gt; based on its content, and then use JavaScript to animate it from zero to that "natural" height, so it appeared to grow vertically from its top edge, revealing its content as it went.&lt;/p&gt;

&lt;p&gt;You wouldn't want the user to see a "flash" of the full-height &lt;code&gt;div&lt;/code&gt; before you set its height back to zero for the start of the animation. &lt;code&gt;useEffect&lt;/code&gt; would display that flash, whereas &lt;code&gt;useLayoutEffect&lt;/code&gt; would let you inspect the height of the rendered &lt;code&gt;div&lt;/code&gt; and then adjust it before the user got to see it.&lt;/p&gt;

&lt;p&gt;In this case, the warning from React would be telling us that&lt;br&gt;
the user would see the flash, because the full-height &lt;code&gt;div&lt;/code&gt; is getting rendered on the server, and the height tweak won't be applied by &lt;code&gt;useLayoutEffect&lt;/code&gt; until the JavaScript starts up on the client – long after the user got to see the server-rendered version of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://react.dev/reference/react/useLayoutEffect#measuring-layout-before-the-browser-repaints-the-screen" rel="noopener noreferrer"&gt;The example given in the official docs&lt;/a&gt; is also good: it involves calculating the position of a tooltip based on the rendered dimensions of the element to which it is attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  See also:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=pHxQtHwcT-s" rel="noopener noreferrer"&gt;A good short video&lt;/a&gt; demonstrating the difference between useEffect and useLayoutEffect.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85" rel="noopener noreferrer"&gt;A good gist&lt;/a&gt; that explains the SSR issue and two available workarounds.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>MelbJS March 2023 notes</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Wed, 08 Mar 2023 10:47:58 +0000</pubDate>
      <link>https://forem.com/cultureamp/melbjs-march-2023-notes-4nli</link>
      <guid>https://forem.com/cultureamp/melbjs-march-2023-notes-4nli</guid>
      <description>&lt;p&gt;Raw notes from the &lt;a href="https://melbjs.com" rel="noopener noreferrer"&gt;MelbJS meetup&lt;/a&gt; held at Culture Amp in Richmond on 8 March, 2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fundamentals of Module Federation, &lt;a href="https://twitter.com/mathewbyrne" rel="noopener noreferrer"&gt;Mathew Byrne&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Microfrontends are an architectural pattern to split up large codebases into small apps that are stitched together into a single page at runtime.&lt;/p&gt;

&lt;p&gt;Problems of a single build (monolith):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scaling build times&lt;/li&gt;
&lt;li&gt;Dependency versioning: Team A wants to upgrade React, Team B isn't ready&lt;/li&gt;
&lt;li&gt;Deployments: slow builds means deploys back up&lt;/li&gt;
&lt;li&gt;In general, bottlenecks!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiple apps: a new bundle per "page". New problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compromised user experience&lt;/li&gt;
&lt;li&gt;Hard to maintain client state across apps&lt;/li&gt;
&lt;li&gt;Scaling challenges still exist within each app&lt;/li&gt;
&lt;li&gt;Duplicate dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Webpack externals: shared dependencies are loaded separately. New problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No on-demand loading of code. All dependencies must be available first.&lt;/li&gt;
&lt;li&gt;Manual juggling of those dependencies.&lt;/li&gt;
&lt;li&gt;Inflexible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Native ES Modules: trust the browser to load your dependencies for you. New problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: round-tripping of cascading requests for dependencies.&lt;/li&gt;
&lt;li&gt;Limited client support.&lt;/li&gt;
&lt;li&gt;Can't use non-ESM dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Module federation: a run-time method of sharing modules between independent builds.&lt;/p&gt;

&lt;p&gt;Terminology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host&lt;/strong&gt;: The first webpack runtime to boot on the client&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote&lt;/strong&gt;: A bundle that the host can dynamically load in when requested.&lt;/li&gt;
&lt;li&gt;These are not mutually exclusive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redundancy, self-healing and versioning come built-in. Already-loaded modules get used. Missing modules get auto-loaded.&lt;/p&gt;

&lt;p&gt;Doesn't interfere with a familiar developer experience.&lt;/p&gt;

&lt;p&gt;Works in the browser and in Node.&lt;/p&gt;

&lt;p&gt;Remote loads are "hoisted": an entire tree of missing dependencies will be loaded in parallel.&lt;/p&gt;

&lt;p&gt;Demo: Two independent webpack apps in a monorepo. Both dev servers running. App1 loads App2 as a remote! App2 is lazy-loaded, but it doesn't have to be. App1's webpack config has a &lt;code&gt;ModuleFederationPlugin&lt;/code&gt; that declares a remote for &lt;code&gt;App2&lt;/code&gt;. App2's webpack config has a &lt;code&gt;ModuleFederationPlugin&lt;/code&gt; that declares that App2 is remote that can be loaded by a host. When App1 loads App2, App2 gets its react, etc. from App1.&lt;/p&gt;

&lt;p&gt;A bundle can be both a host and a remote, which enables some interesting configurations. The module federation project repo has dozens of examples. E.g. Bidirectional, two modules that can each act as a host, but load the other on demand. App Shell, a single host designed to load multiple remotes.&lt;/p&gt;

&lt;p&gt;MF first shipped with Webpack 5 in Oct 2020. Next 13, SSR support are now there. Delegate modules, a new feature like middleware for loading remotes (e.g. dynamic host selection, etc.), just landed.&lt;/p&gt;

&lt;p&gt;Further reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://module-federation.github.io" rel="noopener noreferrer"&gt;https://module-federation.github.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Follow Zack Jackson, &lt;a class="mentioned-user" href="https://dev.to/scriptedalchemy"&gt;@scriptedalchemy&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Standard Promises. Promise Standards, &lt;a href="https://twitter.com/fycdev" rel="noopener noreferrer"&gt;Franky&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If JavaScript is a single-threaded language, then how does async code run?&lt;/p&gt;

&lt;p&gt;Promises are a combination of two specifications: ECMAScript and HTML.&lt;/p&gt;

&lt;p&gt;The browser APIs are async operations processed by the browser engine, which allows the JavaScript engine to keep processing.&lt;/p&gt;

&lt;p&gt;The browser maintains an event loop, background micro-task queues, etc. to make this all work.&lt;/p&gt;

&lt;p&gt;A tour of the relevant specs, using a simple promise example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MelbJS!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;resolveIt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;resolveIt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ew. JavaScript!&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onFulfilled&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works (according to the spec):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check that the &lt;code&gt;Promise&lt;/code&gt; constructor was passed a function as an executor.&lt;/li&gt;
&lt;li&gt;Create an internal promise object, using the Promise prototype, which includes all the promise features we're used to (&lt;code&gt;then&lt;/code&gt;, etc.). Creates some internal "slots" for information about the promise.&lt;/li&gt;
&lt;li&gt;Create the &lt;code&gt;resolve&lt;/code&gt; and &lt;code&gt;reject&lt;/code&gt; functions, which are passed into the executor later.&lt;/li&gt;
&lt;li&gt;Call the executor function. If it completes successfully, it's a normal completion. If it throws and exception, it's an abrupt completion.&lt;/li&gt;
&lt;li&gt;Return the promise!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How the resolving functions (&lt;code&gt;resolve&lt;/code&gt;/&lt;code&gt;reject&lt;/code&gt;) work internally: Depending which is called, the promise will be fulfilled or rejected. If &lt;code&gt;resolve&lt;/code&gt; is called with another promise, that promise is used in turn to continue resolving. Otherwise, the promise is resolved straight away.&lt;/p&gt;

&lt;p&gt;How a promise is fulfilled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[[PromiseState]]: &lt;code&gt;fulfilled&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[[PromiseValue]]: &lt;code&gt;'MelbJS!'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[[PromiseIsHandled]]: &lt;code&gt;false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[[PromiseFulfillReactions]]: &lt;code&gt;undefined&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[[PromiseRejectReactions]]: &lt;code&gt;undefined&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once a promise is fulfilled, it needs to be handled. That's where we move into the &lt;code&gt;then&lt;/code&gt; method. The specification for this function is really, really long! For brevity, we'll ignore all the parts that have to do with rejecting. This is where the HTML spec starts to come in.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;then&lt;/code&gt; method creates another promise, which operates just like the one we've been talking about. It's a bit hidden, but it's what lets us chain promises.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;then&lt;/code&gt; takes a callback, which gets passed to &lt;code&gt;HostMakeJobCallback&lt;/code&gt;, and eventually is transformed into a fulfillReaction. Gets enqueued as a promise reaction job, which is sent to the browser engine – which takes it out of the JavaScript single-threaded execution environment! Its execution is controlled by the browser engine, not JavaScript itself.&lt;/p&gt;

&lt;p&gt;The Event loop processing model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event loop&lt;/li&gt;
&lt;li&gt;Task queue&lt;/li&gt;
&lt;li&gt;Microtask queue&lt;/li&gt;
&lt;li&gt;JavaScript runtime engine (heap and call stack)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are entire talks on this stuff, so we're only going to cover it briefly.&lt;/p&gt;

&lt;p&gt;The JavaScript engine adds code to be executed to its call stack, but the browser event loop can add code to it too. The event loop schedules tasks onto the task queue and microtask queue. Every time the event loop "spins", it checks if a task is still executing in the call stack. If not, it will place a new valid task on the call stack.&lt;/p&gt;

&lt;p&gt;The microtask queue is a priority queue. Being in the task queue is like boarding a plane in Economy Class.&lt;/p&gt;

&lt;p&gt;So, back in the world of the promises standard, &lt;code&gt;HostEnqueuePromiseJob&lt;/code&gt; schedules tasks on the high-priority microtask queue (defined in the HTML spec).&lt;/p&gt;

&lt;p&gt;Another example of how these two standards operate together: &lt;code&gt;HostCallJobCallback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not covered for time (but equally interesting!): stuff like what happens if &lt;code&gt;resolve&lt;/code&gt; is passed another promise. Opens up another can of worms. Also interesting, how browser APIs like &lt;code&gt;fetch&lt;/code&gt; work under the surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pyodide and JS: The One Language to Rule Them All, &lt;a href="https://twitter.com/dr1337" rel="noopener noreferrer"&gt;Hon Weng Chong&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pyodide.org/en/stable/" rel="noopener noreferrer"&gt;Pyodide&lt;/a&gt;: compile Python to WebAssembly.&lt;/p&gt;

&lt;p&gt;Cortical Labs: we grow neurons (live brain cells) and program them to play video games like Pong!&lt;/p&gt;

&lt;p&gt;The goal for us was to abstract away the low-level C that we were writing into a higher-level language like JavaScript or Python. This would enable us to develop APIs to work with the neurons without the need for knowing signal processing, spike detection/sorting, stimulation patterns, rewards, etc.&lt;/p&gt;

&lt;p&gt;Python was the most popular language when we looked, so that's where we started!&lt;/p&gt;

&lt;p&gt;We built a web-stack IDE (Monaco text editor engine).&lt;/p&gt;

&lt;p&gt;Pyodide JS: Two-way communication between JavaScript and Python runtimes. Use Web Workers to prevent Python code from blocking JavaScript. Convenient type conversions for the primitive types supported by both languages.&lt;/p&gt;

&lt;p&gt;Load the Pyodide JS bundle, then write a little bootstrap script and pass it Python source code to run. Just like that, Python running in the browser! No backend, no installation.&lt;/p&gt;

&lt;p&gt;Take the value returned by Pyodide and call its &lt;code&gt;toJS&lt;/code&gt; method to convert the return value of the Python program to JavaScript.&lt;/p&gt;

&lt;p&gt;Can also use &lt;code&gt;pyodide.registerJsModule&lt;/code&gt; to pass a JavaScript value into the Python runtime before running the Python program.&lt;/p&gt;

&lt;p&gt;Demo: a game of Pong implemented in Python, rendering its current state to a Canvas with JS.&lt;/p&gt;

&lt;p&gt;Pyodide is important because it will bring all of the machine learning and AI technologies (implemented in Python) into the Web, where we can give them a UI.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>meetup</category>
    </item>
    <item>
      <title>Web Directions Hover 2022 Day 2 notes</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Tue, 10 May 2022 13:39:27 +0000</pubDate>
      <link>https://forem.com/cultureamp/web-directions-hover-2022-day-1-notes-4f1</link>
      <guid>https://forem.com/cultureamp/web-directions-hover-2022-day-1-notes-4f1</guid>
      <description>&lt;p&gt;These are the live notes I took from Day 2 of the &lt;a href="https://webdirections.org/hover/" rel="noopener noreferrer"&gt;Web Directions Hover 2022&lt;/a&gt; conference for our team internally at Culture Amp.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adrian Bece: “Effective CSS refactoring”
&lt;/h2&gt;

&lt;p&gt;CSS is a simple language, which leaves the door open for many antipatterns. Codebases become more and more difficult to maintain. We want to refactor, but we need to consider some important details to make this practical.&lt;/p&gt;

&lt;p&gt;Netlify &lt;a href="https://www.netlify.com/blog/2021/03/23/from-semantic-css-to-tailwind-refactoring-the-netlify-ui-codebase/" rel="noopener noreferrer"&gt;refactored their codebases&lt;/a&gt; last year (semantic CSS → Tailwind). This article lists all the forms of unwanted complexity that creep into a CSS codebase.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good CSS auditing tool: &lt;a href="https://cssstats.com" rel="noopener noreferrer"&gt;cssstats.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/projectwallace/wallace-cli" rel="noopener noreferrer"&gt;Wallace CLI&lt;/a&gt;: can run in CI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refactoring is best done at quiet times in a codebase that you know will be actively modified going forward. Work with leadership to set aside time for this kind of housekeeping. Do it in a way that doesn’t block feature development. Try to keep tasks small (see: &lt;a href="https://csswizardry.com/2017/06/refactoring-tunnels/" rel="noopener noreferrer"&gt;Refactoring Tunnels&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Refactoring strategy (example: legacy card CSS + global CSS):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select lowest-scope component (in this case, the card rather than the grid in which it is contained)&lt;/li&gt;
&lt;li&gt;Develop styles for the component in isolation (e.g. develop it in CodePen, or prefix class names for this component)&lt;/li&gt;
&lt;li&gt;Merge &amp;amp; fix issues: Replace the markup and add the new CSS; leave the legacy styles for now. If the new styles clash or are broken by old styles, implement temporary fixes in a separate file, that you will delete later.&lt;/li&gt;
&lt;li&gt;Remove legacy CSS, test &amp;amp; deploy: Consider A/B testing to detect any impact on user experience. Use visual regression tooling if you can.&lt;/li&gt;
&lt;li&gt;Repeat for all components, remove the temporary overrides as you go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoiding the need to refactor: discipline, attention to detail, CSS knowledge, well-bounded components. See &lt;a href="https://cssguidelin.es" rel="noopener noreferrer"&gt;cssguidelin.es&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Useful interactive coding challenges: &lt;a href="https://frontendmentor.io" rel="noopener noreferrer"&gt;frontendmentor.io&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Michael Mladky: "Modern CSS rendering performance"
&lt;/h2&gt;

&lt;p&gt;Chrome’s rendering pipeline has 12 steps! &lt;code&gt;contain&lt;/code&gt; and &lt;code&gt;content-visibility&lt;/code&gt; let us skip steps of this rendering pipeline to improve performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scripting&lt;/strong&gt; introduces changes to DOM or Styles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recalculate Styles&lt;/strong&gt; determines what styles change on what elements as a result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout&lt;/strong&gt; calculates the dimensions of elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hit testing&lt;/strong&gt; determines the pointer bounds for mouse interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paint&lt;/strong&gt; generates the pixels for all layers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite&lt;/strong&gt; merges all the layers into one image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Elements have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;border-box: determines the layout dimensions for an element&lt;/li&gt;
&lt;li&gt;visible boundary: determines the area that needs painting&lt;/li&gt;
&lt;li&gt;screen viewport: determines what elements are actually visible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Michael created a testing environment that allowed him to trigger layout and painting.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;contain&lt;/code&gt; is supported in all the major browsers, and has been supported for awhile in most, but just landed in Safari 15.4.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;contain: layout&lt;/code&gt; helps us reduce layouting cost. This has the biggest impact (because it also avoids paints). Layout effects in other parts of the DOM will not trigger layout recalculations for children of this element. Element gets its own stacking context, which can introduce bugs if you aren’t expecting it. Performance is not affected by whether elements are visible within the viewport or not.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;contain: paint&lt;/code&gt; helps to reduce painting cost by not repainting the contents of the element. Crops descendants to the bounds of this element – just like overflow: hidden. Dramatically reduces the render time from changes to those elements – especially when they are offscreen!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;contain: size&lt;/code&gt; reduces the time it takes to calculate element dimensions. But you need to specify dimensions yourself.&lt;/p&gt;

&lt;p&gt;There are also some shorthand values that combine behaviours: &lt;code&gt;contain: content&lt;/code&gt; and &lt;code&gt;contain: strict&lt;/code&gt;. &lt;code&gt;contain: content&lt;/code&gt; usually provides the best combination of performance impact and usability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2r0xd33wpe1lr6krcsrf.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%2F2r0xd33wpe1lr6krcsrf.png" alt="table showing the values of the contain property and their characteristics: layout is easy to use, and has positive performance impact; paint is moderately difficult to use, and has some positive impact; size is hard to use, and has variable impact. The shorthand values: content is moderately difficult to use and has very positive impact on performance; string is hard to use and has variable impact on top of what you get with content." width="607" height="364"&gt;&lt;/a&gt;values of the &lt;code&gt;contain&lt;/code&gt; property and their characteristics&lt;/p&gt;

&lt;p&gt;&lt;code&gt;content-visibility: auto&lt;/code&gt; (Chrome only) lets you skip rendering for elements that are not (yet) in the browser viewport. It even skips CSS property value recalculation for those nodes. You need to specify a static size for these elements (since the browser won’t do the layout to calculate it for you), to avoid scrollbar flickering. This has a dramatic impact on page rendering performance on really large pages, though!&lt;/p&gt;

&lt;p&gt;Michael works at a consultancy that does web application performance audits, and helps companies resolve performance bottlenecks like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ben Buchanan: "Real-world CSS custom props"
&lt;/h2&gt;

&lt;p&gt;Custom properties are just one example of moving on from abstractions to leverage native web platform features.&lt;/p&gt;

&lt;p&gt;Ben works at a company that used many frameworks: Angular, React, Vue, Dash … and whatever comes next. They needed their design system to support even pages that aren’t built with a JavaScript framework!&lt;/p&gt;

&lt;p&gt;Already have multiple colour themes, and expect dark mode coming soon.&lt;/p&gt;

&lt;p&gt;Sass supports themes, has good error handling, etc. but it’s all at compile time. Updates require you to release and import a new version of your style library.&lt;/p&gt;

&lt;p&gt;The team phased out the Sass variables, replacing them with CSS custom props and an HTML API. No one really minded.&lt;/p&gt;

&lt;p&gt;They ship all their themes in a single variables.css file, and then they select the theme (usually per-page on the &lt;code&gt;body&lt;/code&gt; tag). Custom themes are easy: just declare values for a surprisingly small number of CSS custom properties!&lt;/p&gt;

&lt;p&gt;The company considers this a successful “strategic technical investment”. Ben recorded a marketing video about it!&lt;/p&gt;

&lt;p&gt;IE11 was fine. They had some bank customers they needed to support, but they made a graceful degradation decision: IE11 doesn’t get themes: they get the default blue theme for everything.&lt;/p&gt;

&lt;p&gt;Tradeoff: CSS errors silently, but they wired up stylelint to catch typos in custom property names.&lt;/p&gt;

&lt;p&gt;There was some FUD about performance, particularly around &lt;code&gt;calc&lt;/code&gt; expressions using custom properties, but they never ran into any bottlenecks in practice.&lt;/p&gt;

&lt;p&gt;Some war stories of things that didn’t go so well:&lt;/p&gt;

&lt;p&gt;Sass habits: importing your variables at the top of every file like you do in Sass causes the same variable declarations to be output multiple times in your bundle.&lt;/p&gt;

&lt;p&gt;Lesson: Make sure you only bundle in a single copy of your variables.&lt;/p&gt;

&lt;p&gt;…but this broke their linting, because the linter couldn’t find the variable declarations. Thankfully, an open-source add-on for stylelint solved this for them just in time.&lt;/p&gt;

&lt;p&gt;Don’t forget to prefix your variable names, because CSS Custom Properties are a global namespace, and they may clash with other libraries if you’re not careful.&lt;/p&gt;

&lt;p&gt;There’s a subtle mindset shift you have to make from using Sass variables (which are actually variables) and “CSS variables” (Custom Properties), which are more like “configuration to create difference”. They ended up building a cascading set of style token variables, then component-specific variables that use those tokens, which they then referenced in their component styles. This is still evolving and is somewhat confusing, especially if you’re used to thinking in Sass terms. CSS Layers may help make all this cleaner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kathryn Grayson Nanz: "Ditch the Media Queries: Modern CSS Replacements for Better Responsive Code"
&lt;/h2&gt;

&lt;p&gt;We’ve seen a shift from device-based breakpoints to content-based breakpoints. Now with the necessary features landing in browsers, we want to be moving away from breakpoints entirely to fluid design.&lt;/p&gt;

&lt;p&gt;Grid hit full browser support in 2017. Flexbox in 2013. Many developers are unclear on the differences between the two, and assume you should pick one or the other. They’re designed to be complementary, and used together.&lt;/p&gt;

&lt;p&gt;Grid can &lt;code&gt;repeat&lt;/code&gt; columns or rows automatically, and offers &lt;code&gt;auto-fit&lt;/code&gt; to specify column size constraints and have their sizes (and wrapping when necessary) calculated automatically.&lt;/p&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://css-tricks.com/snippets/css/complete-guide-grid/" rel="noopener noreferrer"&gt;A Complete Guide to Grid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gridbyexample.com/" rel="noopener noreferrer"&gt;Grid By Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kathryngraysonnanz/kendo-demo/blob/main/src/app/frame/frame.scss" rel="noopener noreferrer"&gt;frame.scss in the Kendo demo app&lt;/a&gt; – a great real-world example of a complex grid to learn from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flexbox is for laying out items in a single dimension (row or column), with the option to wrap. If you need things to align in two dimensions, you need Grid.&lt;/p&gt;

&lt;p&gt;Resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://flexboxfroggy.com/" rel="noopener noreferrer"&gt;Flexbox Froggy&lt;/a&gt;: a game for learning Flexbox!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flexbox.malven.co/" rel="noopener noreferrer"&gt;FLEX visual cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;aspect-ratio&lt;/code&gt;: Lets you lock an element’s dimensions to an aspect ratio. E.g. Set &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;aspect-ratio&lt;/code&gt; to auto-calculate &lt;code&gt;height&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;min-&lt;/code&gt; and &lt;code&gt;max-&lt;/code&gt; &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;: Often it will be more convenient to use &lt;code&gt;clamp&lt;/code&gt; these days, but these are still useful. Set a “fluid” &lt;code&gt;width&lt;/code&gt; then constrain it with a more restrictive/absolute &lt;code&gt;min-&lt;/code&gt; and/or &lt;code&gt;max-&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clamp&lt;/code&gt;: Set a “base” value, with upper and lower limits. Lets you set responsive limits for values without using breakpoints!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clamp(200px, 50%, 100%)&lt;/code&gt; ← Minimum width of 200px, never exceeding 100% of the container width.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;calc&lt;/code&gt;: Calculate fluid dimensions (e.g. &lt;code&gt;calc(100vw / 3)&lt;/code&gt; for one third of viewport width).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;: Basically &lt;code&gt;clamp&lt;/code&gt; with a single limit.&lt;/p&gt;

&lt;p&gt;Responsive units lets the browser do the work for us. Use them whenever you can!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vh&lt;/code&gt; and &lt;code&gt;vw&lt;/code&gt; are percentages of viewport sizes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rem&lt;/code&gt; and &lt;code&gt;em&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;combining them for responsive font sizes with limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Media queries are still useful as an escape hatch for more niche things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessibility preferences (&lt;code&gt;prefers-reduced-motion&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Completely changing the layout/styles of a component&lt;/li&gt;
&lt;li&gt;Print stylesheets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rachel Andrew: "Responsive patterns with subgrid"
&lt;/h2&gt;

&lt;p&gt;Subgrid is a feature designed to solve the problem you see here: nested grid cells that need to line up with each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77302d297u3m64q4o9mz.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%2F77302d297u3m64q4o9mz.png" alt="A grid of cards, each with three sections that divide the card into title/body/footer sections. The sections of adjacent cards all line up with each other in a visually pleasing way, except for the second card which has more content in its footer, causing that footer to occupy more vertical space, pushing it out of alignment with the implicit grid that otherwise exist across cards" width="506" height="390"&gt;&lt;/a&gt;The second card's footer content pushes it out of alignment with the visible grid&lt;/p&gt;

&lt;p&gt;With basic CSS Grid, only direct children become grid items in a container with &lt;code&gt;display: grid&lt;/code&gt;. Therefore nested elements can’t participate in the grid’s layout.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;display: grid; grid-template-columns: subgrid&lt;/code&gt; will create a nested grid that uses the parent grid’s columns.&lt;/p&gt;

&lt;p&gt;Line names (CSS Grid lets you assign names to lines in your grid, so you can use those names to place items) get passed down into subgrids, so you can use them transparently. You can also add subgrid-specific names to your lines, if you need them.&lt;/p&gt;

&lt;p&gt;Of course subgrids inherit &lt;code&gt;gap&lt;/code&gt;s defined by the parent grid. But you can override this by setting your own &lt;code&gt;gap&lt;/code&gt; in the subgrid. [The effect of setting &lt;code&gt;gap: 0&lt;/code&gt; on a subgrid is a bit like negative margins, giving you a “full bleed” effect. Useful in page layouts? 🤔]&lt;/p&gt;

&lt;p&gt;Something else Subgrid enables is stretching a top-level grid item to stretch over all of the rows of a wrapping implicit grid (where the number of rows is determined by the content). Put the unknown children inside a subgrid!&lt;/p&gt;

&lt;p&gt;Stretching a background to the full page width while keeping the nested content element aligned with the grid is another use case for Subgrid.&lt;/p&gt;

&lt;p&gt;Safari Technology Preview supports subgrid and container queries. You can use them together!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@container&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;.item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;subgrid&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;h2&gt;
  
  
  Stephanie Eckles: "Practical Uses For Container Queries"
&lt;/h2&gt;

&lt;p&gt;Container queries = styling elements based on the available space for them.&lt;/p&gt;

&lt;p&gt;Start by defining a container: &lt;code&gt;container-type: inline-size&lt;/code&gt; is the best supported option right now.&lt;/p&gt;

&lt;p&gt;You can then apply styles to descendants of that container – but not the container itself (but this may change with the spec). Polyfill available!&lt;/p&gt;

&lt;p&gt;Let’s start with a card example. Recommended: your default styles (which display on unsupported browsers) should accommodate the narrowest width. (I.e. mobile-first.)&lt;/p&gt;

&lt;p&gt;Demo: sizing a page with a Flexbox grid. The cards in the grid change their layout type, with the last card (spanning the full width of the bottom row) displaying a different layout than the other cards.&lt;/p&gt;

&lt;p&gt;Demo of setting font sizes with &lt;code&gt;clamp&lt;/code&gt; and &lt;code&gt;cqi&lt;/code&gt; (currently supported as &lt;code&gt;qi&lt;/code&gt; in Chrome Canary) container-relative units.&lt;/p&gt;

&lt;p&gt;Demo of creating a newsletter signup form with three different CSS Grid layouts based on container size. Grid lets you change the positions of the grid items responsively.&lt;/p&gt;

&lt;p&gt;Demo of a pagination component that collapses the clickable page numbers (to “Page x of y”) and the Next/Previous button labels at successively smaller sizes.&lt;/p&gt;

&lt;p&gt;You can name your nested containers, to query based on multiple layers of container sizes.&lt;/p&gt;

&lt;p&gt;Demo of a nav bar with container queries for the overall bar, and also for the menu inside it.&lt;/p&gt;

&lt;p&gt;WCAG “reflow” requirement is for site content to reflow to a single column at 400% zoom (320px equivalent on a 1280px-wide display). Container queries can help with meeting this requirement.&lt;/p&gt;

&lt;p&gt;Future features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Container queries based on the styles applied to a container (e.g. its &lt;code&gt;font-size&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Nicer range syntax (&lt;code&gt;@container (width &amp;gt;= 250px)&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google Chrome Labs has produced &lt;a href="https://github.com/GoogleChromeLabs/container-query-polyfill" rel="noopener noreferrer"&gt;a polyfill for container queries&lt;/a&gt;. Only supports &lt;code&gt;px&lt;/code&gt;-based queries. No support yet for container-relative units.&lt;/p&gt;

&lt;p&gt;Demos here: &lt;a href="http://moderncss.dev/hover22" rel="noopener noreferrer"&gt;moderncss.dev/hover22&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>css</category>
      <category>conference</category>
    </item>
    <item>
      <title>Web Directions Hover 2022 Day 1 notes</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Mon, 02 May 2022 00:45:52 +0000</pubDate>
      <link>https://forem.com/cultureamp/web-directions-hover-2022-day-1-notes-52gi</link>
      <guid>https://forem.com/cultureamp/web-directions-hover-2022-day-1-notes-52gi</guid>
      <description>&lt;p&gt;These are the live notes I took from Day 1 of the &lt;a href="https://webdirections.org/hover/" rel="noopener noreferrer"&gt;Web Directions Hover 2022&lt;/a&gt; conference for our team internally at Culture Amp. Day 2 is this Friday, and I'll aim to do the same!&lt;/p&gt;

&lt;h2&gt;
  
  
  Jen Simmons: “What’s new in ’22 for CSS”
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://caniuse.com" rel="noopener noreferrer"&gt;Caniuse.com&lt;/a&gt; doesn’t just show what’s available now, but “below the line” it shows what’s coming in the next release of each browser.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://wpt.fyi/interop-2022" rel="noopener noreferrer"&gt;Interop 2022 Dashboard&lt;/a&gt; is tracking the big push by the major browsers this year to release the most-requested features to improve CSS interoperability.&lt;/p&gt;

&lt;p&gt;2022 is seeing an unprecedented number of changes land in CSS that will make a material difference to how it works for developers day-to-day.&lt;/p&gt;

&lt;p&gt;The rise of Utility class frameworks [like &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;] are a symptom of CSS not meeting developers’ needs. They are actively avoiding writing CSS. 2022 is seeing a concerted effort to change that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container queries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;aside&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;container-type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@container&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;56rem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* responsive styles here */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(But actually &lt;code&gt;inline-size&lt;/code&gt; is preferable to &lt;code&gt;width&lt;/code&gt; now.)&lt;/p&gt;

&lt;p&gt;You are required to declare what your container is, so that the browser can avoid infinite loops in calculating layout. This is why container queries took so long to happen.&lt;/p&gt;

&lt;p&gt;Best place to learn this is &lt;a href="https://www.w3.org/TR/css-contain-3/" rel="noopener noreferrer"&gt;the actual spec&lt;/a&gt;. The web is littered with obsolete blog posts about earlier ideas for the spec. The spec is actually quite short and readable (and critically, it’s up-to-date).&lt;/p&gt;

&lt;p&gt;New container units: &lt;code&gt;cqw&lt;/code&gt;/&lt;code&gt;cqi&lt;/code&gt; lets you specify a percentage of the container’s &lt;code&gt;width&lt;/code&gt;/&lt;code&gt;inline-size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Container queries are currently behind a flag in Chrome Canary. That’s where the proof-of-concept work on this has been happening for years. It’s also enabled by default in Safari Technical Preview!&lt;/p&gt;

&lt;h3&gt;
  
  
  Cascade layers
&lt;/h3&gt;

&lt;p&gt;Addressing friction caused by selector specificity. Specificity is often not well understood, so Jen gave a brief refresher on how it works.&lt;/p&gt;

&lt;p&gt;Something that is more powerful than specificity: origin. For example, custom styles always override browser styles, and  inline styles always override custom styles, regardless of selector specificity. Cascade layers let you create extra layers: so you could make it so that app-specific styles always override CSS framework [Tailwind?] or design system styles, freeing you from having to worry about selector specificity (or reaching for &lt;code&gt;!important&lt;/code&gt;) to make sure one set of styles override another.&lt;/p&gt;

&lt;p&gt;Shipped in Firefox, Chrome, Safari and Edge, but we may need to wait a little for older browsers (especially mobile browsers) to phase out, since this is difficult to progressively-enhance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parent Selector / &lt;code&gt;:has&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Style a &lt;code&gt;figure&lt;/code&gt; differently depending on if it has a &lt;code&gt;figcaption&lt;/code&gt; as a child:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* … */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;figure&lt;/span&gt;&lt;span class="nd"&gt;:has&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;figcaption&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* … */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also can match direct child (&lt;code&gt;table:has(&amp;gt; thead)&lt;/code&gt;), sibling (&lt;code&gt;dt:has(+ dt)&lt;/code&gt;), and supports negation (&lt;code&gt;section:not(:has(h1, h2, h3))&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Why wasn’t this available before? Computers have gotten faster. We can afford to performance cost of this in today’s browsers/devices.&lt;/p&gt;

&lt;p&gt;Available in Safari 15.4 now. Coming to other browsers in 2022.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS selector nesting
&lt;/h3&gt;

&lt;p&gt;The hardest part of this is not breaking Sass! 😆&lt;/p&gt;

&lt;p&gt;Work underway, but no browser support now. Unlikely to land in 2022.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selector scoping
&lt;/h3&gt;

&lt;p&gt;Most of the docs out there for &lt;code&gt;:scope&lt;/code&gt; on MDN, etc. are now obsolete. That version of scoping is not happening. Early work for &lt;code&gt;@scope&lt;/code&gt; in &lt;a href="https://www.w3.org/TR/css-cascade-6/#scoped-styles" rel="noopener noreferrer"&gt;CSS Cascading Level 6 spec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not landing in 2022.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subgrid for CSS Grid
&lt;/h3&gt;

&lt;p&gt;Lets a child grid align its cells to the rows/columns of a parent element’s grid. (e.g. a &lt;code&gt;ul&lt;/code&gt; can wrap multiple &lt;code&gt;li&lt;/code&gt; cells in a parent &lt;code&gt;div&lt;/code&gt;’s grid).&lt;/p&gt;

&lt;p&gt;Supported for a long time in Firefox. It’s also in Safari Technical Preview. Work underway for Chromium. Hopefully this year!&lt;/p&gt;

&lt;h3&gt;
  
  
  Dialog and &lt;code&gt;::backdrop&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Browsers were slow to implement it because accessibility hasn’t been solved. That seems to have been fixed, so it’s coming to browsers now! Available in all major browsers, with Safari 15.4 the last to implement it [so we can’t quite use it yet].&lt;/p&gt;

&lt;p&gt;Related: HTML &lt;code&gt;inert&lt;/code&gt; attribute lets you make your own “backdrop”-like things: using HTML elements for visual purposes but hiding them from assistive technologies, keyboard shortcuts, etc. Available in Safari TP now, but going to be really useful as it lands in other browsers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Also
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Smarter focus rings with &lt;code&gt;:focus-visible&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Themeing of browser widgets (form controls) with &lt;code&gt;accent-color&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appearance&lt;/code&gt; property to access or completely disable browser-native form element rendering&lt;/li&gt;
&lt;li&gt;and so much more&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bramus Van Damme: “Cascade Layers”
&lt;/h2&gt;

&lt;p&gt;The cascade decides which of multiple matching CSS rules get to “win” when competing to set a property’s value.&lt;/p&gt;

&lt;p&gt;Specificity (or order of appearance when there’s a tie) determines the winner for competing rules with the same: Origin &amp;amp; Importance. But specificity doesn’t always match our intent, so we reach for all sorts of hacks to “bump up” specificity on rules that we want to “win”.&lt;/p&gt;

&lt;p&gt;Layers let us group styles into layers that are meaningful to us, to avoid unwanted specificity/order interactions! For example, we could very easily declare that component styles always override global styles!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://polypane.app/css-specificity-calculator/" rel="noopener noreferrer"&gt;Cool specificity calculator&lt;/a&gt; shared in the conference chat.&lt;/p&gt;

&lt;p&gt;Recommendation: start by putting your reset and third party styles in layers, so your own styles always override them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thirdparty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.bram.us/2021/09/15/the-future-of-css-cascade-layers-css-at-layer/" rel="noopener noreferrer"&gt;Article with all the details from Bramus&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kilian Valkhof, “:where :is :has?”
&lt;/h2&gt;

&lt;p&gt;Previously supported as &lt;code&gt;:matches&lt;/code&gt;, &lt;code&gt;:any&lt;/code&gt;, the opposite to &lt;code&gt;:not&lt;/code&gt; is now called &lt;code&gt;:is&lt;/code&gt;. It also now supports “complex” (multi-element) selectors, where previously it was limited to “simple” (single-element) selectors.&lt;/p&gt;

&lt;p&gt;The ability to specify multiple alternative selectors really cuts down on selector duplication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;nav&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;footer&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;ol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;aside&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* … */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gotcha: &lt;code&gt;:is&lt;/code&gt; can only match DOM elements, not pseudo-elements like &lt;code&gt;::before&lt;/code&gt; or &lt;code&gt;::after&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Kilian made the &lt;a href="https://polypane.app/css-specificity-calculator/" rel="noopener noreferrer"&gt;specificity calculator&lt;/a&gt; I linked above!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:where&lt;/code&gt; is just &lt;code&gt;:is&lt;/code&gt; but with specificity 0.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:is&lt;/code&gt; adds specificity equal to the most specific selector in its list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:has&lt;/code&gt; is the newest, and least supported. But it’s the one that developers have been asking for forever. It’s the so-called “parent” selector. Style an element based on what elements are inside of it.&lt;/p&gt;

&lt;p&gt;(See above for more details on &lt;code&gt;:has&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:has&lt;/code&gt; is called the “relational pseudoclass”, because it lets you target elements based on much more than their descendants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Josh Goldberg, "Focus Visible, At Last!"
&lt;/h2&gt;

&lt;p&gt;An element can receive focus when a user &lt;strong&gt;clicks or tabs&lt;/strong&gt; to an element. You can see which element has focus with a focus ring.&lt;/p&gt;

&lt;p&gt;Some sites don’t like the look of a focus ring, so they remove it with &lt;code&gt;:focus { outline none; }&lt;/code&gt;, which is really bad for users who need these indicators (especially keyboard users!). &lt;strong&gt;This is a red flag. Please don’t do this.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;:focus&lt;/code&gt;, &lt;code&gt;:focus-visible&lt;/code&gt; matches elements that receive focus from the keyboard, not the mouse. So you can use it to show focus rings only when they are needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:focus:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;:focus-visible&lt;/code&gt; landed in Safari 15.4, and is otherwise supported by all our target browsers. So we’re close to being able to drop our JS polyfill for this.&lt;/p&gt;

&lt;p&gt;Design tip: don’t forget your focus indicators when considering colour contrast requirements for accessibility. Your indicators need to satisfy the same minimum contrast ratio!&lt;/p&gt;

&lt;p&gt;Accessibility tip: accessibility overlays like &lt;a href="https://accessibe.com/" rel="noopener noreferrer"&gt;accessiBe&lt;/a&gt; generally don’t work, and may even get you sued. There’s no shortcut to good accessibility. Get yourself dedicated accessibility testers and put real effort into this stuff.&lt;/p&gt;

&lt;p&gt;A good design system to check out that does all this stuff well: &lt;a href="https://gamut.codecademy.com" rel="noopener noreferrer"&gt;Codecademy Gamut&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Martine Dowden, "Type-setting in CSS"
&lt;/h2&gt;

&lt;p&gt;A brief primer/history on typography and fonts. Serif vs Sans Serif, Display and Monospace fonts.&lt;/p&gt;

&lt;p&gt;You might think that fonts generate variants like bold, italic, etc. algorithmically, but in truth most fonts define glyphs for each of these variants entirely separately.&lt;/p&gt;

&lt;p&gt;Leading with &lt;code&gt;line-height&lt;/code&gt; to improve legibility.&lt;/p&gt;

&lt;p&gt;Drop-caps for embellishment with &lt;code&gt;::first-letter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Over/under/through lines with &lt;code&gt;text-decoration&lt;/code&gt;, which can specify position, style (doubled, dotted, dashed, wavy!), width. Some combinations not fully supported.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ch&lt;/code&gt; unit is nice for setting &lt;code&gt;max-width&lt;/code&gt; [or &lt;code&gt;max-inline-size&lt;/code&gt;] based on a number of average character widths. &lt;code&gt;39ch&lt;/code&gt; = 1.5 alphabets, which is a good width for readability.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;letter-spacing&lt;/code&gt; (to crowd or space characters) is a useful, and under-used visual effect.&lt;/p&gt;

&lt;p&gt;[Sadly I missed the end of this talk because of network/system issues on my Mac. 😢]&lt;/p&gt;

&lt;h2&gt;
  
  
  Alvaro Montoro, "The colorful future of CSS"
&lt;/h2&gt;

&lt;p&gt;[Sadly I also missed much of this talk for the same technical issue. 😞]&lt;/p&gt;

&lt;p&gt;&lt;code&gt;color-contrast&lt;/code&gt; function will auto-select colours based on WCAG contrast requirements for accessibility!&lt;/p&gt;

&lt;p&gt;E.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;color-contrast&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;#000&lt;/span&gt; &lt;span class="nt"&gt;vs&lt;/span&gt; &lt;span class="err"&gt;#222&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#eee&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;#ff0&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="nt"&gt;AA&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it with CSS custom properties (variables) to auto select from your design system colours. Or pick a white or black text based on which will provide sufficient contrast against the background! &lt;code&gt;color-contrast&lt;/code&gt; will automatically choose white or black if none of the listed colours meet contrast requirements!&lt;/p&gt;

&lt;p&gt;Whoa. We’re effectively getting a super-powered &lt;code&gt;calc&lt;/code&gt; for colours so you can mix, match and override colour channels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;rgb&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="nt"&gt;hsl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;180&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%)&lt;/span&gt; &lt;span class="nt"&gt;r&lt;/span&gt; &lt;span class="nt"&gt;g&lt;/span&gt; &lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make a 50% opacity version of a design system colour!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--design-system-color&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;r&lt;/span&gt; &lt;span class="nt"&gt;g&lt;/span&gt; &lt;span class="nt"&gt;b&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Elad Shechter, "How does CSS work?"
&lt;/h2&gt;

&lt;p&gt;Default styles come from the user agent stylesheet, right…? &lt;code&gt;span&lt;/code&gt; by default is &lt;code&gt;display: inline&lt;/code&gt;. Why don’t you see a user agent stylesheet entry for this in the browser inspector? Interesting question!&lt;/p&gt;

&lt;p&gt;To answer this, we need to understand these special values supported by all CSS properties: &lt;code&gt;inherit&lt;/code&gt;, &lt;code&gt;initial&lt;/code&gt;, &lt;code&gt;unset&lt;/code&gt;, &lt;code&gt;revert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;inerit&lt;/code&gt; – use the value of the property from the parent component.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;initial&lt;/code&gt; – Every CSS property has an “initial value”, which is the calculated value you get if you don’t set the property at all (e.g. &lt;code&gt;position: static&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;div { display: initial; }&lt;/code&gt; will make the &lt;code&gt;div&lt;/code&gt; &lt;code&gt;display: inline&lt;/code&gt;. This is the answer to the &lt;code&gt;span&lt;/code&gt; question above! The initial value is specific to the property, not the element type.&lt;/p&gt;

&lt;p&gt;You can see the initial value for each property in MDN (or your favourite CSS reference).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unset&lt;/code&gt; – Sometimes behaves like &lt;code&gt;inherit&lt;/code&gt; (on properties that inherit by default), or &lt;code&gt;initial&lt;/code&gt; (on properties that don’t inherit by default).&lt;/p&gt;

&lt;p&gt;CSS has two kinds of properties: “inherited” and “uninherited”. Inherited are all the typography properties – we want a &lt;code&gt;strong&lt;/code&gt; element inside a &lt;code&gt;p&lt;/code&gt; to inherit the text styles of the &lt;code&gt;p&lt;/code&gt;. Properties like this are shown as “Inherited: yes” on MDN.&lt;/p&gt;

&lt;p&gt;“uninherited” properties are all the other properties. Box, position, display, transforms… everything, really. These are shown as “Inherited: no” on MDN.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;color: unset&lt;/code&gt; → &lt;code&gt;inherit&lt;/code&gt;&lt;br&gt;
&lt;code&gt;padding: unset&lt;/code&gt; → &lt;code&gt;initial&lt;/code&gt; → &lt;code&gt;0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Why have this one value that can mean both things? The special &lt;code&gt;all&lt;/code&gt; property, which you can use to reset all properties at once: &lt;code&gt;all: unset&lt;/code&gt;. Useful when you have a block that you want to be treated as “unstyled HTML” – a one-line CSS reset!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.common-content&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;all&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unset&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;…but wait, how do you unset a property like &lt;code&gt;display&lt;/code&gt;, whose user agent stylesheet defines a value depending on the element type?&lt;/p&gt;

&lt;p&gt;This is what the &lt;code&gt;revert&lt;/code&gt; value does! &lt;code&gt;div { display: revert; }&lt;/code&gt; sets the &lt;code&gt;div&lt;/code&gt; back to &lt;code&gt;display: block&lt;/code&gt; based on the user agent stylesheet.&lt;/p&gt;

&lt;p&gt;Using this, built &lt;a href="https://elad2412.github.io/the-new-css-reset/" rel="noopener noreferrer"&gt;The New CSS Reset&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Override default form element styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;all&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c"&gt;/* custom checkbox styles */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>css</category>
      <category>conference</category>
    </item>
    <item>
      <title>How to test for screen reader accessibility with VoiceOver on your Mac</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Tue, 26 Apr 2022 11:11:26 +0000</pubDate>
      <link>https://forem.com/cultureamp/how-to-test-for-screen-reader-accessibility-with-voiceover-on-your-mac-148n</link>
      <guid>https://forem.com/cultureamp/how-to-test-for-screen-reader-accessibility-with-voiceover-on-your-mac-148n</guid>
      <description>&lt;p&gt;Every Mac has a screen reader built in, called VoiceOver. This article will describe how you can use it to test the web application user interfaces you build!&lt;/p&gt;

&lt;p&gt;If you prefer a video, this is a recording of a brown bag session I gave at &lt;a href="https://dev.to[we're%20hiring!][CA%20hiring]"&gt;Culture Amp&lt;/a&gt; in early 2019, when we started getting serious about web accessibility. (We've come a very long way since then!) Besides introducing the features of VoiceOver, I do an ad hoc test of Culture Amp’s engagement survey experience as it existed at that time.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/38qQzmkXGS4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Culture Amp's survey experience no longer relies on a "basic capture" mode to accommodate screen reader users. In 2020, we shipped updates to the main survey experience that fixed all the screen reader issues we (and our auditor, &lt;a href="https://intopia.digital" rel="noopener noreferrer"&gt;Intopia&lt;/a&gt;) could find. This is a &lt;em&gt;far&lt;/em&gt; preferable user experience for users of assistive technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Every Mac has VoiceOver included, so there’s nothing to install. To turn on VoiceOver, hit &lt;strong&gt;⌘F5&lt;/strong&gt;. Hit the same shortcut again to turn it off. On Macs with a TouchBar (where the function keys are not visible by default), you can instead hold down command key (&lt;strong&gt;⌘&lt;/strong&gt;) and press the &lt;strong&gt;TouchID button&lt;/strong&gt; three times in quick succession (click-click-click) to toggle VoiceOver on or off.&lt;/p&gt;

&lt;p&gt;When VoiceOver starts up, you’ll hear a voice explain your current context, and your options for what to do next. If you have a Safari browser window in focus, it might sound like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VoiceOver on Safari. Welcome to Engagement Survey, window. Welcome to Engagement Survey, web content has keyboard focus.&lt;/p&gt;

&lt;p&gt;You are currently on web content. To enter the web area, press Control-Option-Shift-Down Arrow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll also see a black border around the part of the user interface (in this example, the Safari window’s content area) that currently has screen reader focus. This is also known as the &lt;strong&gt;VoiceOver cursor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The message above suggests pressing &lt;strong&gt;^⌥⇧↓&lt;/strong&gt; (Control-Option-Shift-Down Arrow). The &lt;strong&gt;^⌥&lt;/strong&gt; (Control-Option) part is known as the VoiceOver Modifier, and it’s the two keys you will hold down for almost every command that you send to VoiceOver.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you find &lt;strong&gt;^⌥&lt;/strong&gt; difficult to remember, by default VoiceOver will also respond to the &lt;strong&gt;⇪&lt;/strong&gt; (Caps Lock) key. So in the above example you could hold &lt;strong&gt;⇪⇧↓&lt;/strong&gt; to enter the web area.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;^⌥⇧↓&lt;/strong&gt; is the standard shortcut to interact with a group, like entering the Safari browser’s web content area. If you use this to enter a web page, you’ll hear something like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Welcome to Engagement Survey, web content.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll also see the VoiceOver cursor will shrink to enclose just the first element of the web page.&lt;/p&gt;

&lt;p&gt;To get VoiceOver to read the current contents of the cursor, you can hit &lt;strong&gt;^⌥F3&lt;/strong&gt;. To do this on a TouchBar Mac, you’ll need to hold the &lt;strong&gt;Fn&lt;/strong&gt; key to see the function keys on the touch bar, then you’ll need to tap and then double-tap the &lt;strong&gt;F3&lt;/strong&gt; key – tapping it helps you identify that you’ve tapped the right key (VoiceOver will say “F3, button”), and enables it for activation by double-tapping.&lt;/p&gt;

&lt;p&gt;It might say something like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CA-logo_coral, group, is in the VoiceOver cursor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this example, the VoiceOver cursor is on an image element that lacks a useful alternative text description, so VoiceOver reads out the filename of the image instead. Not very useful, is it? Congratulations! You’ve found your first screen reader accessibility bug!&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving around
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;^⌥→&lt;/strong&gt; moves the VoiceOver cursor forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^⌥←&lt;/strong&gt; moves the VoiceOver cursor backward.&lt;/p&gt;

&lt;p&gt;Each time you navigate, VoiceOver will describe the content in the cursor, then give you a hint of what you can do next.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hooli logo, image.&lt;/p&gt;

&lt;p&gt;You are currently on an image, inside of web content. To exit this web area, press Control-Option-Shift-Up Arrow.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These hints can get tedious, and there are a couple of options to fix this:&lt;/p&gt;

&lt;p&gt;At any time, you can interrupt VoiceOver by tapping the &lt;strong&gt;^&lt;/strong&gt; (Control) key or &lt;strong&gt;⇪&lt;/strong&gt; (Caps Lock). You can tap again if you want to resume playback.&lt;/p&gt;

&lt;p&gt;Alternatively you can open VoiceOver Utility (in &lt;strong&gt;System → Applications → Utilities&lt;/strong&gt;, or &lt;strong&gt;^⌥F8&lt;/strong&gt; with VoiceOver running). Under &lt;strong&gt;Verbosity → Hints&lt;/strong&gt;, you can disable &lt;strong&gt;Speak instructions for using the item in the VoiceOver cursor.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^⌥⇧N&lt;/strong&gt; immediately plays the hint for the current cursor position, which is handy once you’ve disabled playing them automatically.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;^⌥⇧↑&lt;/strong&gt; is the standard shortcut to exit, or stop interacting with a group. It’s the opposite of the “interact with a group” shortcut we used above to enter the web page. These two shortcuts let you move into and out of groups (and sub-groups) of elements anywhere on a web page.&lt;/p&gt;

&lt;p&gt;By default, when you encounter a group while moving forward or backward through a document, VoiceOver will enter groups and read their contents to you one at a time. To skip over groups as you navigate, just hold down the &lt;strong&gt;⇧&lt;/strong&gt; (shift) key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^⌥⇧→&lt;/strong&gt; moves the VoiceOVer cursor forward, skipping over groups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^⌥⇧←&lt;/strong&gt; moves the VoiceOVer cursor backward, skipping over groups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interacting with links and buttons
&lt;/h2&gt;

&lt;p&gt;When you get to an interactive element like a link or button, you’ll hear something like this (including the hint):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get started, button&lt;/p&gt;

&lt;p&gt;You are currently on a button, inside of web content. To click this button, press Control-Option-Space. To exit this web area, press Control-Option-Shift-Up Arrow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;^⌥␣&lt;/strong&gt; is the standard shortcut to trigger an interactive element. It’s just like hitting the spacebar on your keyboard to trigger a button after tabbing to it with your keyboard, but with the VoiceOver modifier added.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading in chunks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;^⌥A&lt;/strong&gt; will read the content of the element selected by the VoiceOver cursor. It’s a good way to read an entire group without navigating into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The VoiceOver Rotor
&lt;/h2&gt;

&lt;p&gt;Screen reader users frequently jump around the page using prominent landmarks such as headings and form controls. The VoiceOver Rotor is a convenient interface for doing this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;^⌥U&lt;/strong&gt; opens the VoiceOver Rotor, a two-dimensional menu for navigating page landmarks. The first time you open it, you’ll see the &lt;strong&gt;Headings&lt;/strong&gt; menu, showing all of the headings on the page, and their level (1-6). You can use the up and down arrows to navigate the list, or you can type to filter the list with a search string. Hit &lt;strong&gt;Enter&lt;/strong&gt; to jump to a selected heading in the list, or hit &lt;strong&gt;Escape&lt;/strong&gt; to exit the list.&lt;/p&gt;

&lt;p&gt;With the Rotor open, you can view other menus of page landmarks: &lt;strong&gt;Links&lt;/strong&gt;, &lt;strong&gt;Form Controls&lt;/strong&gt;, etc.&lt;/p&gt;

&lt;p&gt;VoiceOver’s QuickNav feature (which can be disabled if you want) remembers which list you last had open in the Rotor, and lets you quickly jump between the items in that list using the &lt;strong&gt;↑&lt;/strong&gt; and &lt;strong&gt;↓&lt;/strong&gt; keys (without the VoiceOver modifier). This makes it super easy to get around a page using headings, for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group navigation mode
&lt;/h2&gt;

&lt;p&gt;VoiceOver has an alternative navigation mode that lets you move around with the arrow keys (&lt;strong&gt;^⌥↑&lt;/strong&gt;, &lt;strong&gt;^⌥↓&lt;/strong&gt;, &lt;strong&gt;^⌥←&lt;/strong&gt;, &lt;strong&gt;^⌥→&lt;/strong&gt;), giving you a sense of spacial awareness about the page. If we were being really thorough with our screen reader testing, we’d have to test both modes! In practice, we’re told by the VoiceOver manual, different sites often work better with different modes, so it’s common for users to toggle this setting as they move from site to site.&lt;/p&gt;

&lt;p&gt;To turn on Group navigation mode, hit &lt;strong&gt;^⌥H&lt;/strong&gt; to open the VoiceOver Help menu, then &lt;strong&gt;^⌥H&lt;/strong&gt; again to get to the Commands Help menu. Type “DOM” to locate the Toggle Web Navigation DOM or Group and hit Enter to toggle the option on.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Groups, web navigation&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(You can toggle back to DOM navigation mode using the same steps.)&lt;/p&gt;

&lt;p&gt;You can now use the four arrow keys (with the VoiceOver modifier) to move around the page and have VoiceOver read the entire content of each major content block as you do.&lt;/p&gt;

&lt;p&gt;Remember, to get in and out of groups, use &lt;strong&gt;^⌥⇧↓&lt;/strong&gt; and &lt;strong&gt;^⌥⇧↑&lt;/strong&gt;, respectively.&lt;/p&gt;

&lt;p&gt;If in doubt, get the page working well with DOM navigation mode, as it’s the default setting for VoiceOver, and is closest to how other screen readers (like JAWS and NVDA for Windows) work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;You can click the little question mark button in VoiceOver Utility to open the &lt;a href="https://help.apple.com/voiceover/info/guide/10.9/English.lproj/index.html" rel="noopener noreferrer"&gt;VoiceOver User Guide&lt;/a&gt;, which is the best way to get to know VoiceOver better. The most worthwhile sections to start reading are &lt;a href="https://help.apple.com/voiceover/info/guide/10.9/English.lproj/_1195.html" rel="noopener noreferrer"&gt;Introducing VoiceOver&lt;/a&gt;, &lt;a href="https://help.apple.com/voiceover/info/guide/10.9/English.lproj/_1198.html" rel="noopener noreferrer"&gt;Learning VoiceOver Basics&lt;/a&gt;, and &lt;a href="https://help.apple.com/voiceover/info/guide/10.9/English.lproj/_1205.html" rel="noopener noreferrer"&gt;Browsing the Internet&lt;/a&gt;.&lt;br&gt;
The WebAIM website has &lt;a href="https://webaim.org/articles/voiceover/" rel="noopener noreferrer"&gt;a guide very similar to this one&lt;/a&gt;, that contains some nuggets of information you might not find here.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>apple</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ally Beaton looks back on two years in the Kaizen design system</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Thu, 24 Feb 2022 09:24:33 +0000</pubDate>
      <link>https://forem.com/cultureamp/ally-beaton-looks-back-on-two-years-in-the-kaizen-design-system-3d9b</link>
      <guid>https://forem.com/cultureamp/ally-beaton-looks-back-on-two-years-in-the-kaizen-design-system-3d9b</guid>
      <description>&lt;p&gt;On her second-last day as &lt;a href="https://www.cultureamp.com" rel="noopener noreferrer"&gt;Culture Amp&lt;/a&gt;'s tech lead for the&lt;br&gt;
&lt;a href="https://cultureamp.design" rel="noopener noreferrer"&gt;Kaizen design system&lt;/a&gt;, &lt;a href="https://twitter.com/trycatchally" rel="noopener noreferrer"&gt;Ally Beaton&lt;/a&gt; joined me to look back on two years of progress in our React component library:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Gn6w_nE0eYk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you're interested in being Kaizen's next tech lead, check out &lt;a href="https://boards.greenhouse.io/cultureamp/jobs/3880125" rel="noopener noreferrer"&gt;the job ad&lt;/a&gt; and please apply!&lt;/p&gt;

</description>
      <category>designsystems</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Micro front ends, strategy, and blueberry muffins</title>
      <dc:creator>Kevin Yank</dc:creator>
      <pubDate>Fri, 14 Jan 2022 05:22:40 +0000</pubDate>
      <link>https://forem.com/cultureamp/micro-front-ends-strategy-and-blueberry-muffins-404e</link>
      <guid>https://forem.com/cultureamp/micro-front-ends-strategy-and-blueberry-muffins-404e</guid>
      <description>&lt;p&gt;As the director of Culture Amp's large-and-growing front end engineering practice, my biggest challenge in the past year was to communicate a coherent strategy that those 60+ engineers could understand and get behind.&lt;/p&gt;

&lt;p&gt;The solution was &lt;strong&gt;blueberry muffins&lt;/strong&gt;. 🫐&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the table
&lt;/h2&gt;

&lt;p&gt;I've worked at &lt;a href="https://www.cultureamp.com/" rel="noopener noreferrer"&gt;Culture Amp&lt;/a&gt; for nearly seven years because I believe that empowering companies to put their people first, to make their culture a competitive advantage, makes a difference in the world that I can be proud of.&lt;/p&gt;

&lt;p&gt;We build a set of web-based products that together we call an employee experience platform. It's a growing collection of React apps to 1) collect employee feedback to drive engagement, 2) run performance reviews that are fair and equitable, and 3) develop the most important workplace skills in managers and individual contributors.&lt;/p&gt;

&lt;p&gt;When I joined Culture Amp in 2015 I was employee number 26, we had a single engineering team, and we had yet to land our 200th client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsps6n7pc9e69wc6us4ms.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%2Fsps6n7pc9e69wc6us4ms.png" alt="screenshot" width="800" height="999"&gt;&lt;/a&gt;How it started: Culture Amp Insight report circa 2015&lt;/p&gt;

&lt;p&gt;Seven years later, over 5,000 companies around the world now use our products, which are built and maintained by over 100 engineers across a dozen or so different teams.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel8rk5r1zdm5sarmc9sh.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%2Fel8rk5r1zdm5sarmc9sh.png" alt="screenshot" width="800" height="929"&gt;&lt;/a&gt;How it's going: the same screen in 2022 – with fake data, of course&lt;/p&gt;

&lt;p&gt;Safe to say, our front end engineers today build a lot of UIs – new and old, routine and novel – all at once. From that comes our biggest challenge. For it to go well (by which I mean we build user interfaces (UIs) affordably, sustainably, and producing a product our customers love) we must empower those engineers to work in separate codebases, but embrace every opportunity we have to &lt;strong&gt;leverage consistency across those codebases&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Did I say codebases? I meant blueberry muffins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shopping list
&lt;/h2&gt;

&lt;p&gt;To articulate a strategy for the practice, I had to start with where we are. For five years and counting, Culture Amp has had a group of engineers devoted to the health and scalability of front end engineering. Here's what they've built so far:&lt;/p&gt;

&lt;p&gt;Back in 2017, we began with &lt;a href="https://cultureamp.design" rel="noopener noreferrer"&gt;our design system: Kaizen&lt;/a&gt;. A set of design guidelines, atomic values (design tokens), and more than 50 reusable components (with many more to come!), Kaizen defines the consistent look and feel of our product. There's always more to do, but Kaizen is already in wide and successful use in all our UI code bases today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg4bb1m6ikb3kjsq9unt0.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%2Fg4bb1m6ikb3kjsq9unt0.png" alt="screenshot of the Kaizen home page" width="800" height="732"&gt;&lt;/a&gt;&lt;a href="https://cultureamp.design/" rel="noopener noreferrer"&gt;The Kaizen website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kicking off a couple of years later in 2019, Culture Amp's standard development, continuous integration, and serverless infrastructure deployment tooling for UIs is called frontend-ops. It's still an internal project at this stage so I can't link to it, but here's a peek at some of its features:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkkglh7wcos1t2sxcdsnu.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%2Fkkglh7wcos1t2sxcdsnu.png" alt="screenshot listing yarn commands to run a local development server, create a static build, deploy to production, run a Storybook server, and run tools like TypeScript, eslint, Jest, Prettier and Playwright" width="800" height="668"&gt;&lt;/a&gt;From the README of Culture Amp's front end app template&lt;/p&gt;

&lt;p&gt;So what's missing?&lt;/p&gt;

&lt;p&gt;Culture Amp has settled on &lt;a href="https://reactjs.org" rel="noopener noreferrer"&gt;React&lt;/a&gt; written in &lt;a href="https://www.typescriptlang.org" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; as the standard platform for all of its UIs. But React is just a view framework, which leaves a lot of problems for engineers to solve in building a web app. Routing, data fetching, state management, form validation… there are at least a dozen more things that every Culture Amp UI needs, but which React doesn't provide. Having every team reinvent these wheels for every project is not a good use of their time. In short, we need a more full-featured framework for our React applications.&lt;/p&gt;

&lt;p&gt;At the same time, there are things we need to learn and get better at together as a practice. Not because we're bad at them, but because the art of web application engineering is always progressing, and we need to keep up. Test automation, web accessibility, application security, animation and other user experience refinements, performance optimizations, and driving runtime error rates down.&lt;/p&gt;

&lt;p&gt;All this and more I wrote about in a sweeping vision for the many ways we would improve as a practice in the months and years ahead. It was all in there. Would it provide the clarity my fellow engineers were craving?&lt;/p&gt;

&lt;p&gt;One of Culture Amp's values is to "Learn faster through feedback", so in July 2021 or so I shared my latest draft with a handful of front end engineers and engineering leaders around the company, and asked what they thought.&lt;/p&gt;

&lt;p&gt;I got a ton of great feedback, much of which I might share in a separate article about writing strategy documents if folks are curious. But my single biggest take-away that crystallized in a conversation with my manager, &lt;a href="https://www.linkedin.com/in/ridgwaymichael/" rel="noopener noreferrer"&gt;Michael Ridgway&lt;/a&gt; (thanks, Mike!), was that I was missing exactly that: one memorable concept to pull the whole thing together.&lt;/p&gt;

&lt;p&gt;Rather than a shopping list of necessary ingredients for the success of our practice, &lt;strong&gt;I needed to give readers one idea to remember&lt;/strong&gt;, and carry with them into their work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A delicious metaphor
&lt;/h2&gt;

&lt;p&gt;So yes, a blueberry muffin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oizcse8h59tpuc4fytw.jpeg" 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%2F9oizcse8h59tpuc4fytw.jpeg" alt="Close-up photo of a blueberry muffin surrounded by others like it. The blueberries are labelled " width="800" height="535"&gt;&lt;/a&gt;A very delicious muffin, despite appearances!&lt;br&gt;Photo borrowed from &lt;a href="https://www.biggerbolderbaking.com/lemon-blueberry-muffins/" rel="noopener noreferrer"&gt;Gemma's Bigger Bolder Baking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quoting from my final strategy document:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If our front end apps are blueberry muffins, we want to bake a lot of them while keeping them consistently delicious. To do this, we’ve got a ready-made “muffin cup” we can use on every muffin (frontend-ops), and we’ve locked in a single source for our “blueberries” (Kaizen components). &lt;strong&gt;Next, we need to work on the recipe for our “batter” (our React app architecture/framework).&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Kaizen components are blueberries&lt;/strong&gt; because they're the colourful morsels that festoon the surface of our "muffins". When you decide to bake a blueberry muffin, you &lt;em&gt;definitely&lt;/em&gt; don't have time to plant a blueberry bush and wait for it to grow, so you need a reliable source of high-quality blueberries. Likewise, no one wants to build a date picker from scratch for their new app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend-ops is the paper cup&lt;/strong&gt; because it's the easily-overlooked conveyance device that speeds the muffin on its journey from oven to mouth. Most &lt;del&gt;UI engineers&lt;/del&gt; bakers couldn't make one from scratch even if they wanted to – it's not even food!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The app framework is the batter&lt;/strong&gt; because it's not the &lt;em&gt;point&lt;/em&gt; of the muffin – it's the blueberries that make your mouth water, not the cake – but it holds it all together. Without it, you'd just have a pile of blueberries. One bowl of batter makes an entire batch of muffins: you don't make new batter for each muffin!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The muffin itself represents the app&lt;/strong&gt; because it's the product we're ultimately trying to offer. No two are exactly alike, but a consistent set of ingredients ensures that every muffin can deliver the same quality despite its uniqueness.&lt;/p&gt;

&lt;p&gt;This single metaphor captures the one most important thing we seek to achieve (make our front end apps more alike, from architecture to pixels), the two aspects that are already going well (UI components and serverless architecture), and the one missing piece that we need everyone to rally to create (a shared application framework).&lt;/p&gt;

&lt;p&gt;Since I published this strategy and presented it at leadership and practice meetings, not to mention numerous engineering inductions since, it's become known as the "blueberry muffin document", and engineers have begun using shorthand like "is this project going to contribute to our 'muffin batter'?" This, more than anything, is the signal that tells me the strategy has landed, and is meaningful to the engineers I wrote it for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crumbs on the floor
&lt;/h2&gt;

&lt;p&gt;What about all the other things that we're also working towards as a practice: test automation and accessibility, performance and error rates? Were they sacrificed in service of a satisfying metaphor?&lt;/p&gt;

&lt;p&gt;Well that's the thing about a strategy: it isn't just a list of everything you want. It needs to narrow the focus, elevating a just few things &lt;em&gt;even over&lt;/em&gt; all the other also-desirable things.&lt;/p&gt;

&lt;p&gt;Beyond the overriding priorities covered by the blueberry muffin, the document lists five &lt;a href="https://www.fearlessculture.design/blog-posts/establish-clear-priorities-using-even-over-statements" rel="noopener noreferrer"&gt;"even overs"&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Grow test automation to reduce manual testing burden&lt;/li&gt;
&lt;li&gt;Improve platform accessibility&lt;/li&gt;
&lt;li&gt;Harden our UIs with new browser security features&lt;/li&gt;
&lt;li&gt;Polish the details of our user experience&lt;/li&gt;
&lt;li&gt;Grow performance, shrink errors&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;For each of these it describes the current situation, efforts already being made to progress it, and advice for how engineers passionate about it can contribute to driving it forward.&lt;/p&gt;

&lt;p&gt;You can get a lot done with 60+ engineers, and I'm confident we will progress significantly all five of these areas in the coming year. But none of them is as important as perfecting our blueberry muffin recipe. &lt;/p&gt;

&lt;h2&gt;
  
  
  A muffin for the road
&lt;/h2&gt;

&lt;p&gt;Am I saying that you should embrace this same muffin metaphor if you happen to be writing a strategy for &lt;em&gt;your&lt;/em&gt; engineering practice? Not at all!&lt;/p&gt;

&lt;p&gt;But if you need to pull together a lot of threads into something memorable, do look for that unifying metaphor. Have some fun with it. Got a strong coffee culture in your team? Maybe you're trying to make the perfect macchiato! Like ice cream? Who doesn't!? What might a &lt;a href="https://www.magnumicecream.com/" rel="noopener noreferrer"&gt;Magnum&lt;/a&gt; say about your architecture?&lt;/p&gt;

&lt;p&gt;There are probably even some non-food-related metaphors to consider; I'm just a little hungry right now. Honestly, I started with chocolate chip cookies until I realised that those aren't baked in a tray of identical cups!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3f5o6cz4esb5ny91qou4.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%2F3f5o6cz4esb5ny91qou4.png" alt="A Slack conversation between Kevin Yank and Louis Quinnell. Kevin writes " width="589" height="80"&gt;&lt;/a&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%2F5e2iythbzvg7ftxnv3bl.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%2F5e2iythbzvg7ftxnv3bl.png" alt="Louis suggests " width="589" height="52"&gt;&lt;/a&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%2Fduhxlfl4uk78rqdm2aeb.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%2Fduhxlfl4uk78rqdm2aeb.png" alt="Kevin replies, " width="589" height="385"&gt;&lt;/a&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%2F62hsi6exonj72thd4kq1.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%2F62hsi6exonj72thd4kq1.png" alt="Louis reasons, " width="589" height="97"&gt;&lt;/a&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%2Fvq48djzo0pcps1pwdejm.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%2Fvq48djzo0pcps1pwdejm.png" alt="Kevin replies, " width="589" height="421"&gt;&lt;/a&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%2Fuf8a0gl0u60q78m6lstm.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%2Fuf8a0gl0u60q78m6lstm.png" alt="Louis quips, " width="589" height="87"&gt;&lt;/a&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%2F5xwjb80rhwt24sufll16.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%2F5xwjb80rhwt24sufll16.png" alt="Kevin observes, " width="589" height="245"&gt;&lt;/a&gt;Working out the kinks with Louis Quinnell, the tech lead for frontend-ops&lt;/p&gt;

&lt;p&gt;This, like so many things, is an opportunity to create culture. Have some fun with it!&lt;/p&gt;

&lt;p&gt;As you might imagine, we think of this kind of thing a lot at Culture Amp. If you like thinking of engineering problems through a lens of company culture, &lt;a href="https://www.cultureamp.com/company/careers" rel="noopener noreferrer"&gt;please apply!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Conversation with Jason O'Neil
&lt;/h2&gt;

&lt;p&gt;The delivery lead of Culture Amp's Front End Ops team, &lt;a href="https://jasono.co" rel="noopener noreferrer"&gt;Jason O'Neil&lt;/a&gt; sat down with me for a brief chat about his career, and how it intersects with the subject of this blog post:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/p2G5oFWjZsQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Thanks to &lt;a href="https://unsplash.com/@anetvob" rel="noopener noreferrer"&gt;Aneta Voborilova&lt;/a&gt; for the &lt;a href="https://unsplash.com/photos/aFqKIFZ-Idg" rel="noopener noreferrer"&gt;cover image&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>leadership</category>
      <category>webdev</category>
      <category>react</category>
      <category>designsystems</category>
    </item>
  </channel>
</rss>
