<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Agnel Nieves</title>
    <description>The latest articles on DEV Community by Agnel Nieves (@agnelnieves).</description>
    <link>https://dev.to/agnelnieves</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%2F168324%2Fb8ed8b67-086e-4f89-b626-cb7aa4d92b87.jpg</url>
      <title>DEV Community: Agnel Nieves</title>
      <link>https://dev.to/agnelnieves</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agnelnieves"/>
    <language>en</language>
    <item>
      <title>Rust Owns the JavaScript Toolchain in 2026</title>
      <dc:creator>Agnel Nieves</dc:creator>
      <pubDate>Thu, 14 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/agnelnieves/rust-owns-the-javascript-toolchain-in-2026-l9b</link>
      <guid>https://dev.to/agnelnieves/rust-owns-the-javascript-toolchain-in-2026-l9b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vzmnz9w2ns2afgdxc0l.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%2F3vzmnz9w2ns2afgdxc0l.png" alt="abstract lines artwork" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;If you are shipping a modern JavaScript app in 2026, almost every step between your editor and your production bundle is a Rust binary. Next.js builds with Turbopack. Vite builds with Rolldown. Your linter and formatter are Biome or oxlint. Your CSS pipeline runs through Lightning CSS. Bun, the runtime that started in Zig, just merged its Rust rewrite into main. The result: bundlers are 5 to 30x faster, linters are 50 to 100x faster, and the dependency tree of "the JavaScript toolchain" has collapsed to a handful of statically linked binaries with zero npm postinstall scripts. That last detail matters more than it sounds, because the npm supply chain spent the last 18 months getting set on fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm writing this
&lt;/h2&gt;

&lt;p&gt;I noticed it in pieces. I shipped &lt;a href="https://agnelnieves.com/blog/building-a-terminal-portfolio-you-can-ssh-into" rel="noopener noreferrer"&gt;a terminal portfolio over SSH&lt;/a&gt; in Rust two weeks ago, and the build pipeline felt like a different planet to anything I had touched in Node years ago. Then &lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;Next.js 16&lt;/a&gt; went stable with Turbopack as the default. Then &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;Vite 8&lt;/a&gt; shipped with Rolldown. Then &lt;a href="https://github.com/oven-sh/bun/pull/30412" rel="noopener noreferrer"&gt;Anthropic merged a Zig-to-Rust port of Bun&lt;/a&gt;, mostly written with AI. Then a fresh &lt;a href="https://www.aikido.dev/blog/mini-shai-hulud-is-back-tanstack-compromised" rel="noopener noreferrer"&gt;Mini Shai-Hulud wave&lt;/a&gt; hit TanStack, Mistral, and 160+ npm packages three days ago, on the heels of a &lt;a href="https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack" rel="noopener noreferrer"&gt;November Shai-Hulud campaign&lt;/a&gt; that compromised roughly 25,000 GitHub repos. "Small dependency" is now a polite phrase for "unsandboxed code with full filesystem access."&lt;/p&gt;

&lt;p&gt;Three threads, one direction. Worth writing down.&lt;/p&gt;

&lt;p&gt;I am late to this take. Lee Robinson wrote &lt;a href="https://leerob.com/rust" rel="noopener noreferrer"&gt;"Rust Is Eating JavaScript"&lt;/a&gt; and the 2026 update made the bigger point cleanly. The angles I want to add are mobile and security, because the toolchain story is only the first act.&lt;/p&gt;

&lt;h2&gt;
  
  
  The toolchain went Rust
&lt;/h2&gt;

&lt;p&gt;Pick a step in the build pipeline and chances are it has been quietly rewritten in Rust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turbopack.&lt;/strong&gt; &lt;a href="https://nextjs.org/blog/next-16" rel="noopener noreferrer"&gt;Next.js 16&lt;/a&gt; (February 2026) shipped Turbopack as the default bundler for both &lt;code&gt;next dev&lt;/code&gt; and &lt;code&gt;next build&lt;/code&gt;. The numbers Vercel published before the cutover: more than half of all dev sessions and more than a fifth of production builds were already on Turbopack via 15.3+. After the default flipped, the 16.2 release in April claimed roughly 87% faster dev startup on real apps. The HMR feels different. I had stopped noticing how often I was alt-tabbing during compiles, and after switching I noticed how often I was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rolldown.&lt;/strong&gt; &lt;a href="https://vite.dev/blog/announcing-vite8" rel="noopener noreferrer"&gt;Vite 8&lt;/a&gt; shipped on March 12, 2026 with &lt;a href="https://voidzero.dev/posts/announcing-rolldown-1-0" rel="noopener noreferrer"&gt;Rolldown&lt;/a&gt; as the bundler, taking over from the Rollup plus esbuild combination. Public production numbers from the Rolldown team: Excalidraw dropped from 22.9s to 1.4s. PLAID, an internal product at ByteDance, dropped from 80s to 5s. Both are roughly 16x. The transitional &lt;code&gt;rolldown-vite&lt;/code&gt; package was archived a week after 8.0 because it was redundant. If you &lt;code&gt;npm create vite@latest&lt;/code&gt; today, you get Rolldown without asking for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rspack.&lt;/strong&gt; ByteDance's webpack-compatible &lt;a href="https://www.infoq.com/news/2026/01/rspack-final-rust/" rel="noopener noreferrer"&gt;Rust bundler hit 1.7 in January&lt;/a&gt; and is on the runway to 2.0. Internally they ship TikTok, Douyin, Lark, and Coze on it. Externally, Microsoft, Amazon, and Discord are in the public adopter list. The migration cases I have seen all land in the same neighborhood: 9x faster cold dev, 3x less memory, prod builds under 4 seconds on apps that used to take 30.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Biome and oxlint.&lt;/strong&gt; ESLint is no longer the default lint stack. &lt;a href="https://biomejs.dev/blog/biome-v2/" rel="noopener noreferrer"&gt;Biome 2&lt;/a&gt; added type-aware rules that do not require running the TypeScript compiler. &lt;a href="https://oxc.rs/blog/2025-06-10-oxlint-stable" rel="noopener noreferrer"&gt;Oxlint 1.0 went stable in August&lt;/a&gt;, ran 50 to 100x faster than ESLint on the same rulesets, and shipped a &lt;a href="https://voidzero.dev/posts/announcing-oxlint-type-aware-linting-alpha" rel="noopener noreferrer"&gt;type-aware alpha&lt;/a&gt; in March on top of &lt;code&gt;tsgolint&lt;/code&gt; (which itself runs on Microsoft's Go-based &lt;code&gt;tsgo&lt;/code&gt;). Shopify reported a 71% lint-time reduction across an ~80,000 file TypeScript codebase. The pattern teams actually use: oxlint as a pre-commit and CI gate for the hot rules, Biome for formatting, ESLint kept around for any plugin you cannot get rid of yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lightning CSS.&lt;/strong&gt; &lt;a href="https://tailwindcss.com/blog/tailwindcss-v4" rel="noopener noreferrer"&gt;Tailwind v4&lt;/a&gt;'s "Oxide" engine is &lt;a href="https://github.com/parcel-bundler/lightningcss" rel="noopener noreferrer"&gt;Lightning CSS&lt;/a&gt; underneath. Full builds 5x faster, incremental builds up to 100x. The PostCSS chain is gone from the default setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bun.&lt;/strong&gt; Bun was a Zig project. In May, Anthropic (which acquired Bun last year and uses it inside Claude Code) &lt;a href="https://github.com/oven-sh/bun/pull/30412" rel="noopener noreferrer"&gt;merged a port of the entire codebase from Zig to Rust&lt;/a&gt; into main. The port is roughly 966,000 lines, it was largely AI-assisted, and it passes 99.8% of the existing test suite on Linux x64 glibc. Bun 1.3.14 is positioned as the last Zig release. The reasons given: Rust's memory safety story, and Zig's no-AI contribution policy clashing with how Anthropic builds tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deno and Node.&lt;/strong&gt; Deno's core was always Rust, and the &lt;a href="https://deno.com/blog/v2.6" rel="noopener noreferrer"&gt;2.6 release in December&lt;/a&gt; bumped V8 to 14.2, added &lt;code&gt;dx&lt;/code&gt; to replace &lt;code&gt;npx&lt;/code&gt;, and started using &lt;code&gt;tsgo&lt;/code&gt; for type checking. Node 25.2 (November 2025) shipped stable TypeScript support through &lt;a href="https://github.com/nodejs/amaro" rel="noopener noreferrer"&gt;Amaro 1.0&lt;/a&gt;, which is a wrapper around &lt;code&gt;@swc/wasm-typescript&lt;/code&gt;. Node's official type-stripping path is now SWC compiled to WebAssembly, shipping inside core. There is more Rust running inside &lt;code&gt;node&lt;/code&gt; than most teams realize.&lt;/p&gt;

&lt;p&gt;If you list the build steps in your CI pipeline (lint, format, type check, transpile, bundle, minify, optimize CSS) the only step still being done by JavaScript on JavaScript code in 2026 is type checking. And the most consequential type checker (Microsoft's official one) is &lt;a href="https://devblogs.microsoft.com/typescript/typescript-native-port/" rel="noopener noreferrer"&gt;being ported to Go&lt;/a&gt;, not Rust, by the team that wrote TypeScript in the first place. That is the one part of the ecosystem where the obvious "rewrite it in Rust" bet did not pay off, and it is worth saying out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bigger than dev tools
&lt;/h2&gt;

&lt;p&gt;This is bigger than JavaScript. Microsoft has &lt;a href="https://azure.microsoft.com/en-us/blog/microsoft-azure-security-evolution-embrace-secure-multitenancy-confidential-compute-and-rust/" rel="noopener noreferrer"&gt;publicly committed to embracing Rust across Azure infrastructure&lt;/a&gt;, and Azure CTO Mark Russinovich has spent the last two years telling every conference audience that new kernel development at Microsoft should stop using C or C++. New code for Windows and Azure should be written in Rust. That is not a future commitment. Production components already moved: parts of &lt;code&gt;Win32k.sys&lt;/code&gt;, Hyper-V, the &lt;a href="https://www.microsoft.com/en-us/research/blog/rewriting-symcrypt-in-rust-to-modernize-microsofts-cryptographic-library/" rel="noopener noreferrer"&gt;SymCrypt cryptographic library&lt;/a&gt;, and Azure Data Explorer. The &lt;a href="https://devblogs.microsoft.com/azure-sdk/azure-sdk-release-march-2026/" rel="noopener noreferrer"&gt;Azure SDK for Rust&lt;/a&gt; shipped beta in February 2025 and now releases monthly alongside the other language SDKs. At &lt;a href="https://thenewstack.io/microsoft-goes-all-in-on-rust-for-core-infrastructure-and-much-more/" rel="noopener noreferrer"&gt;RustConf 2025&lt;/a&gt;, Microsoft's Galen Hunt described the broader internal goal as refactoring roughly one million lines of code per month for the rest of the decade, with the aim of eliminating C and C++ from Microsoft's codebase by 2030.&lt;/p&gt;

&lt;p&gt;The Linux kernel has merged Rust drivers since 6.1 (late 2022) and the &lt;a href="https://rust-for-linux.com/" rel="noopener noreferrer"&gt;Rust-for-Linux subsystem keeps growing each release&lt;/a&gt;. AWS rewrote &lt;a href="https://github.com/firecracker-microvm/firecracker" rel="noopener noreferrer"&gt;Firecracker&lt;/a&gt;, its microVM monitor, in Rust years ago and continues to add Rust services across the stack. Google has Rust in &lt;a href="https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html" rel="noopener noreferrer"&gt;Android&lt;/a&gt;, in parts of Chromium, and in Fuchsia. When the kernel your build tools run on is moving to Rust, the build tools moving to Rust is the predictable next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about mobile
&lt;/h2&gt;

&lt;p&gt;Mobile is a different shape and Rust shows up differently.&lt;/p&gt;

&lt;p&gt;The default cross-platform stacks (React Native, Flutter) are not Rust. React Native's New Architecture (Fabric, TurboModules, JSI) is C++. Hermes is C++. Flutter is Dart plus C++. What changed in the last 18 months is the layer just above that: Rust as a shared core, exposed to those frameworks through binding generators.&lt;/p&gt;

&lt;p&gt;The pattern teams actually use: &lt;strong&gt;write the things that should not be rewritten twice in Rust, and put a thin native UI on top of them.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signal&lt;/strong&gt; does this with &lt;a href="https://github.com/signalapp/libsignal" rel="noopener noreferrer"&gt;&lt;code&gt;libsignal&lt;/code&gt;&lt;/a&gt;. The protocol, AES-GCM and other primitives, zero-knowledge groups, and remote attestation are all Rust crates. Java, Swift, and TypeScript wrappers expose them to Android, iOS, and Desktop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1Password&lt;/strong&gt; rewrote its core (sync, storage, crypto, permissions) in Rust, kept native UI per platform, and built &lt;a href="https://1password.com/blog/typeshare-for-rust" rel="noopener noreferrer"&gt;TypeShare&lt;/a&gt; so type definitions stay consistent across the FFI boundary. One Rust core, eight clients on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mozilla&lt;/strong&gt; ships sync, login storage, browsing history, push, and experimentation as Rust components shared between Firefox iOS and Firefox Android, all generated through &lt;a href="https://github.com/mozilla/uniffi-rs" rel="noopener noreferrer"&gt;UniFFI&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; ships &lt;a href="https://github.com/cloudflare/boringtun" rel="noopener noreferrer"&gt;BoringTun&lt;/a&gt;, a userspace WireGuard implementation in Rust, on millions of consumer iOS and Android devices through Mozilla VPN and others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bitwarden&lt;/strong&gt; is moving its cryptographic operations into a shared &lt;code&gt;bitwarden_core&lt;/code&gt; Rust SDK consumed by every client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new bit, and the reason this thread is worth pulling on right now: in December 2024 Mozilla and Filament released &lt;a href="https://hacks.mozilla.org/2024/12/introducing-uniffi-for-react-native-rust-powered-turbo-modules/" rel="noopener noreferrer"&gt;&lt;strong&gt;Uniffi for React Native&lt;/strong&gt;&lt;/a&gt;. It generates a TurboModule (TypeScript plus the JSI C++ glue) directly from a Rust crate and a UniFFI interface definition. That finally aligns React Native with the pattern Signal and 1Password have been using productively for years. Flutter has had &lt;a href="https://github.com/fzyzcjy/flutter_rust_bridge" rel="noopener noreferrer"&gt;&lt;code&gt;flutter_rust_bridge&lt;/code&gt;&lt;/a&gt; doing the same job for a while, and v2 is now an officially Flutter Favorite package with async Rust support, web targets, and zero-copy big arrays.&lt;/p&gt;

&lt;p&gt;The Rust-native UI frameworks (Dioxus, Slint, egui) are real and improving. &lt;a href="https://slint.dev/blog/slint-1.12-released" rel="noopener noreferrer"&gt;Slint added an iOS tech preview in 1.12&lt;/a&gt; last June, with proper Xcode integration, simulator and device deploy, TestFlight, and App Store publishing. Dioxus 0.7 runs on iOS reasonably well and on Android with some pain (Huawei and Airbus are public production references). None of these are what I would reach for to build a polished consumer app today, but the trade is no longer "no Rust GUI on phones." It is "yes, but you should know what you are signing up for."&lt;/p&gt;

&lt;p&gt;The honest tradeoffs on mobile have not gone away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Toolchain pain.&lt;/strong&gt; NDK plus &lt;code&gt;cargo-ndk&lt;/code&gt; for Android. Xcode, codesigning, provisioning, and a sometimes fragile &lt;code&gt;lipo&lt;/code&gt;/XCFramework dance for iOS. Every Rust target multiplies your CI matrix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;String impedance.&lt;/strong&gt; Rust is UTF-8, Swift is UTF-16, Java/Kotlin is "modified UTF-8." Conversions are easy to get wrong, and "wrong" means corruption or crashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FFI overhead is real.&lt;/strong&gt; The well-worn guidance: batch your calls. One call processing 100 items beats 100 calls processing one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary size.&lt;/strong&gt; A naive Rust core can add MB to your IPA or APK before optimization. LTO, &lt;code&gt;panic=abort&lt;/code&gt;, and symbol stripping are the price of entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform API drift.&lt;/strong&gt; Widgets, App Intents, Live Activities, Material You, and the latest media APIs almost always live outside the Rust core, in native code. Apple and Google ship features faster than any binding generator can chase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The takeaway for mobile is the same as on the desktop side: Rust as a shared core works. Rust as the whole app on mobile is niche.&lt;/p&gt;

&lt;h2&gt;
  
  
  And then there is npm
&lt;/h2&gt;

&lt;p&gt;This is the part of the story I do not think gets told enough.&lt;/p&gt;

&lt;p&gt;The last 18 months were brutal for the npm registry. Three events worth grounding the rest of this on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 8, 2025: &lt;a href="https://www.aikido.dev/blog/npm-debug-and-chalk-packages-compromised" rel="noopener noreferrer"&gt;qix / chalk + debug&lt;/a&gt;.&lt;/strong&gt; A maintainer named Josh Junon (handle &lt;code&gt;qix&lt;/code&gt;) owns some of the most foundational packages in JavaScript: &lt;code&gt;chalk&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;ansi-styles&lt;/code&gt;, &lt;code&gt;strip-ansi&lt;/code&gt;, &lt;code&gt;color-convert&lt;/code&gt;, and a dozen more. He received a phishing email from &lt;code&gt;support@npmjs.help&lt;/code&gt;, a lookalike domain registered three days earlier. The attacker captured his credentials and TOTP through an adversary-in-the-middle session and published malicious versions of 18 packages with combined weekly downloads of roughly 2 billion. &lt;a href="https://www.exiger.com/perspectives/a-single-compromise-threatened-34-percent-of-npm/" rel="noopener noreferrer"&gt;Exiger estimated the transitive blast radius&lt;/a&gt; at around 34% of the entire npm registry. The payload was a browser-side crypto-clipper that hooked &lt;code&gt;window.ethereum&lt;/code&gt;, &lt;code&gt;window.solana&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt;, and &lt;code&gt;XHR&lt;/code&gt;, then swapped wallet addresses in outgoing transactions using minimum Levenshtein distance so the substitution would survive a quick visual check. Aikido flagged it within five minutes. &lt;a href="https://www.sygnia.co/threat-reports-and-advisories/npm-supply-chain-attack-september-2025/" rel="noopener noreferrer"&gt;The malicious versions were on the registry&lt;/a&gt; and being installed by CI worldwide for the entire window before that. Vercel published a same-day &lt;a href="https://vercel.com/blog/critical-npm-supply-chain-attack-response-september-8-2025" rel="noopener noreferrer"&gt;advisory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;September 15, 2025: &lt;a href="https://unit42.paloaltonetworks.com/npm-supply-chain-attack/" rel="noopener noreferrer"&gt;Shai-Hulud&lt;/a&gt;.&lt;/strong&gt; A week later, &lt;code&gt;@ctrl/tinycolor&lt;/code&gt; (2M+ weekly downloads) was found to contain a self-replicating worm. The malware was the first true worm in the npm ecosystem: every install used the victim's npm token to enumerate other packages the maintainer owned, injected a Webpack-bundled payload, bumped the version, and republished. By the time &lt;a href="https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem" rel="noopener noreferrer"&gt;CISA issued its alert on September 23&lt;/a&gt;, more than 500 packages were affected, including ones from CrowdStrike, &lt;code&gt;@nativescript-community&lt;/code&gt;, and &lt;code&gt;@operato&lt;/code&gt;. The payload ran TruffleHog against the host to scrape AWS keys, GCP credentials, Azure tokens, GitHub PATs, and npm tokens, then uploaded them to public GitHub repos named "Shai-Hulud." &lt;a href="https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack" rel="noopener noreferrer"&gt;A second wave on November 24&lt;/a&gt; moved its hook from &lt;code&gt;postinstall&lt;/code&gt; to &lt;code&gt;preinstall&lt;/code&gt; (so it ran before any user-visible install output) and hit roughly 25,000 GitHub repositories. &lt;a href="https://www.kb.cert.org/vuls/id/534320" rel="noopener noreferrer"&gt;CERT/CC VU#534320&lt;/a&gt; called it the first credential-stealing, self-propagating worm in npm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;May 11, 2026: &lt;a href="https://www.aikido.dev/blog/mini-shai-hulud-is-back-tanstack-compromised" rel="noopener noreferrer"&gt;Mini Shai-Hulud&lt;/a&gt;.&lt;/strong&gt; Three days ago, as I write this. A threat actor StepSecurity has been tracking as TeamPCP &lt;a href="https://tanstack.com/blog/npm-supply-chain-compromise-postmortem" rel="noopener noreferrer"&gt;hit TanStack hard&lt;/a&gt;: 42 packages, 84 malicious versions. The same campaign caught Mistral AI, Guardrails AI, UiPath, OpenSearch, and the Bitwarden CLI. Socket flagged 416 affected packages in total; Aikido catalogued 373 malicious package-version entries across 169 names. The technique was novel: a &lt;code&gt;pull_request_target&lt;/code&gt; workflow that ran attacker-controlled fork code, GitHub Actions cache poisoning across the fork/base trust boundary, and OIDC token extraction from the runner's process memory. Once inside maintainer CI, the worm did what its predecessor did: scrape credentials, find publishable packages, inject, republish.&lt;/p&gt;

&lt;p&gt;The detail that mattered most: the malicious TanStack packages were &lt;a href="https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem" rel="noopener noreferrer"&gt;the first documented npm malware shipping with valid SLSA provenance attestations&lt;/a&gt;. The cryptographic chain worked perfectly. The attacker had simply stolen the GitHub OIDC token used to sign builds. Provenance proves "this package was built by this CI run." It does not prove the CI run was not compromised.&lt;/p&gt;

&lt;p&gt;The earlier baseline (the December 2023 &lt;a href="https://www.ledger.com/blog/security-incident-report" rel="noopener noreferrer"&gt;Ledger &lt;code&gt;connect-kit&lt;/code&gt; attack&lt;/a&gt; that drained DeFi front-ends for about five hours, the &lt;a href="https://www.sonatype.com/blog/lottie-player-compromised-in-supply-chain-attack-all-you-need-to-know" rel="noopener noreferrer"&gt;LottieFiles compromise in October 2024&lt;/a&gt;, the &lt;a href="https://www.sonatype.com/blog/npm-packages-rspack-vant-compromised-blocked-by-sonatype" rel="noopener noreferrer"&gt;Rspack token theft in December 2024&lt;/a&gt;) is what made September 2025 land as a confirmation, not a surprise. May 2026 made it a pattern.&lt;/p&gt;

&lt;p&gt;The structural reason these keep happening: &lt;strong&gt;npm is built on install-time arbitrary code execution and ambient authority.&lt;/strong&gt; The &lt;code&gt;preinstall&lt;/code&gt;, &lt;code&gt;install&lt;/code&gt;, and &lt;code&gt;postinstall&lt;/code&gt; hooks run before any human can review them, with full read and write filesystem access and full network. A typical app's &lt;code&gt;node_modules&lt;/code&gt; is several thousand packages from several hundred maintainers, any one of whom can phish or token-leak their way into your build. A logger and a color formatter have the same operating-system privileges as your application code, because Node has no capability-based security to draw a line between them.&lt;/p&gt;

&lt;p&gt;The protective measures that shipped (npm provenance, expanded required 2FA in October and November 2025, scan tools like Socket and OSV-Scanner) help at the margins but do not change the model. A phished maintainer with a valid provenance signature still ships valid signed malware. Mini Shai-Hulud proved that this week, with attestations the GitHub Actions runner generated honestly while compromised. 2FA does not stop an adversary-in-the-middle proxy. Detection time has tightened from days to minutes, which is real progress, but the first thousand installs of a poisoned package still happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this makes Rust tooling load-bearing
&lt;/h2&gt;

&lt;p&gt;Moving the build toolchain to Rust does not fix the structural problems in your runtime dependencies. Your app's &lt;code&gt;node_modules&lt;/code&gt; is still a forest of trust assumptions. What it does change is that the &lt;strong&gt;build&lt;/strong&gt; toolchain (the most-installed, most-privileged surface area in the average project) leaves the npm graph entirely.&lt;/p&gt;

&lt;p&gt;A linter, a formatter, a bundler, a test runner, and a transpiler are pure dev-time tools with read access to your whole codebase and write access to your machine. They are also among the most-installed packages on npm, which is exactly what made their maintainers attractive phishing targets. Replacing them with one statically linked Rust binary collapses that surface. There is no &lt;code&gt;postinstall&lt;/code&gt;. There is no transitive graph. There is a single maintainer organization whose key you can pin.&lt;/p&gt;

&lt;p&gt;Concrete numbers from a clean &lt;code&gt;npm install&lt;/code&gt; I ran this week:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Toolchain choice&lt;/th&gt;
&lt;th&gt;Top-level deps&lt;/th&gt;
&lt;th&gt;Total packages&lt;/th&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;eslint&lt;/code&gt; + &lt;code&gt;prettier&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;20 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@biomejs/biome&lt;/code&gt; (Rust)&lt;/td&gt;
&lt;td&gt;1 platform binary wrapper&lt;/td&gt;
&lt;td&gt;1 user-facing&lt;/td&gt;
&lt;td&gt;48 MB binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rollup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4.5 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;rolldown&lt;/code&gt; (Rust)&lt;/td&gt;
&lt;td&gt;3 platform binary wrapper&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Biome's own docs report that a realistic ESLint + Prettier + typical plugins setup lands closer to 127 to 200 packages once plugin ecosystems get involved. The 59 above is the bare minimum.&lt;/p&gt;

&lt;p&gt;Cargo has its own supply-chain risks (typo-squats, the long-running debate about &lt;code&gt;serde&lt;/code&gt;'s precompiled binaries, the occasional malicious crate). What it does not have is a postinstall hook that fires arbitrary code on every developer machine for every transitive build dependency, on every install, forever. That is the difference that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust specifically
&lt;/h2&gt;

&lt;p&gt;Two reasons. The performance numbers are the easy half: 5 to 30x for bundlers, 50 to 100x for linters, 3 to 4x lower memory across the board. That alone would explain the move.&lt;/p&gt;

&lt;p&gt;The harder half is everything else.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory safety without a GC.&lt;/strong&gt; Long-running dev servers and HMR processes do not want to pause for 200ms at the wrong moment. GC-based runtimes (Go, JS) hit this; Rust does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-binary distribution.&lt;/strong&gt; CLIs like &lt;code&gt;oxlint&lt;/code&gt; and &lt;code&gt;biome&lt;/code&gt; ship as one executable per platform. No Node bootstrap, no thousand &lt;code&gt;require()&lt;/code&gt; calls, no JS launcher script wrapping a native binary wrapping the actual logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fearless parallelism.&lt;/strong&gt; &lt;code&gt;rayon&lt;/code&gt; and &lt;code&gt;tokio&lt;/code&gt; let linters and bundlers fan out across cores cleanly. The JS event loop is fast but it is one core at a time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebAssembly as an escape hatch.&lt;/strong&gt; Node 25.2 ships SWC as wasm inside core. Browser playgrounds run Oxc directly. The same Rust crate can serve a CLI, a Node API, and a browser sandbox without rewriting any of the hot path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also worth saying clearly: Rust is not the only winner. TypeScript's compiler is &lt;a href="https://devblogs.microsoft.com/typescript/typescript-native-port/" rel="noopener noreferrer"&gt;being ported to &lt;strong&gt;Go&lt;/strong&gt;&lt;/a&gt;, not Rust, by Anders Hejlsberg and the team that wrote TypeScript in the first place. &lt;a href="https://visualstudiomagazine.com/articles/2026/04/21/typescript-7-0-beta-arrives-on-go-based-foundation-with-10x-speed-claim.aspx" rel="noopener noreferrer"&gt;The 7.0 beta dropped on April 21, 2026&lt;/a&gt; with a 10x speed claim. Bun started in &lt;strong&gt;Zig&lt;/strong&gt; and only just switched. The honest story is "native code ate JS tooling, mostly Rust, but not entirely."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would build with this stack today
&lt;/h2&gt;

&lt;p&gt;If I were starting a new app this week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;next@16&lt;/code&gt; or &lt;code&gt;vite@8&lt;/code&gt; for the framework. Both Rust bundlers underneath.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@biomejs/biome&lt;/code&gt; for formatting plus a lint baseline. &lt;code&gt;oxlint&lt;/code&gt; as a CI gate for the type-aware rules.&lt;/li&gt;
&lt;li&gt;Tailwind v4 with the Oxide engine.&lt;/li&gt;
&lt;li&gt;A Rust core library for anything cryptographic, anything sync-heavy, or anything I would want to share between web and mobile, exposed through UniFFI or &lt;code&gt;flutter_rust_bridge&lt;/code&gt; if mobile is on the roadmap.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bun&lt;/code&gt; or &lt;code&gt;pnpm&lt;/code&gt; as the package manager. Lockfile discipline matters more than the manager you pick.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole pipeline would have one or two Node processes that exist mainly to host the dev server and run user code. Everything else underneath would be Rust binaries doing the heavy work. That is not aspirational anymore. That is just &lt;code&gt;npm create&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limitations
&lt;/h2&gt;

&lt;p&gt;Three things I would rather not glaze over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin ecosystems fragment.&lt;/strong&gt; Every Rust tool eventually faces the same fork: stay in Rust (fast, inaccessible to most JS contributors) or expose a JS plugin API (slower, ergonomic). Rolldown went hard on Rollup-plugin compatibility for adoption. Oxlint shipped a JS plugin alpha in March because too many ESLint rules people care about live in plugin land. Marvin Hagemeister's &lt;a href="https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-11/" rel="noopener noreferrer"&gt;Speeding up the JavaScript ecosystem&lt;/a&gt; series is the canonical write-up of this tension and worth reading if you are considering migrating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The contributor bus factor is real.&lt;/strong&gt; Rust is a learning curve for JS-native maintainers. Concentration on Turbopack, Oxc, and Biome is worth pricing in if you are building on top of these tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory regressions happen.&lt;/strong&gt; Rolldown 1.0 RC.18 shipped with about 7x higher RSS than Vite 7 in dev for some apps before the fixes landed. Bundlers in Rust are not automatically more memory-efficient. They are faster, but the wins live in CPU time, not necessarily memory.&lt;/p&gt;

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

&lt;p&gt;If you have not migrated a project off ESLint yet, &lt;code&gt;bunx @biomejs/biome init&lt;/code&gt; plus &lt;code&gt;bun add -d oxlint&lt;/code&gt; will take you 15 minutes. The Biome migration commands (&lt;code&gt;biome migrate eslint&lt;/code&gt;, &lt;code&gt;biome migrate prettier&lt;/code&gt;) are doing the right thing in 2026. The Vite 8 upgrade is a &lt;code&gt;npm install vite@8&lt;/code&gt; for most apps. Next.js 16 is the same story; the Turbopack flag is just gone now.&lt;/p&gt;

&lt;p&gt;The faster builds are the part people notice. The smaller dependency tree is the part your security team should notice. The shared Rust core that could ship to mobile next quarter is the part your CTO should notice.&lt;/p&gt;

&lt;p&gt;It all started with one team being annoyed enough at webpack to write SWC. We got here by accident.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://agnelnieves.com/about" rel="noopener noreferrer"&gt;Agnel Nieves&lt;/a&gt;, a design engineer with 15+ years across product, design systems, and crypto. More writing on &lt;a href="https://agnelnieves.com/blog" rel="noopener noreferrer"&gt;the blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Building a Terminal Portfolio You Can SSH Into</title>
      <dc:creator>Agnel Nieves</dc:creator>
      <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/agnelnieves/building-a-terminal-portfolio-you-can-ssh-into-3h9b</link>
      <guid>https://dev.to/agnelnieves/building-a-terminal-portfolio-you-can-ssh-into-3h9b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcfn5z48vqdpwoj796z1.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%2Fhcfn5z48vqdpwoj796z1.png" alt="Screenshot of agnel's terminal with the ssh cli" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;You can now browse this site in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh agnelnieves.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install. One Rust binary that runs as either a local CLI or an in-binary SSH server. No system sshd, no user accounts, no auth. The TUI is built with &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt;; the SSH layer is &lt;a href="https://docs.rs/russh" rel="noopener noreferrer"&gt;russh&lt;/a&gt;; render code is shared between local and remote modes. It's hosted on Fly.io behind a dedicated IPv4, and the whole thing costs about $2/mo. The code lives as a &lt;code&gt;cli/&lt;/code&gt; package inside the same Next.js project that powers this site, so the website and the terminal share a single deploy story. If you want the full build (architecture, code, Fly.io setup), I wrote it up as a &lt;a href="https://agnelnieves.com/guides/ssh-terminal-portfolio.md" rel="noopener noreferrer"&gt;step-by-step guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ssh agnelnieves.sh&lt;/code&gt;. That's it. No &lt;code&gt;-l user@&lt;/code&gt;, no keys to add, no first-time signup. The server accepts whatever username your local SSH client offers and drops you straight into a terminal UI.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;h&lt;/code&gt;/&lt;code&gt;a&lt;/code&gt;/&lt;code&gt;b&lt;/code&gt;/&lt;code&gt;c&lt;/code&gt; to jump between Home, About, Blog, and Connect. &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; (or arrows) to navigate lists. &lt;code&gt;Enter&lt;/code&gt; to open. &lt;code&gt;q&lt;/code&gt; to quit, which closes the SSH session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Honestly? I saw &lt;a href="https://terminal.shop" rel="noopener noreferrer"&gt;terminal.shop&lt;/a&gt; and thought it was awesome. They sell coffee at &lt;code&gt;ssh terminal.shop&lt;/code&gt; (that's the whole storefront, no website) and the first time I tried it I wanted to know if I could build something like it.&lt;/p&gt;

&lt;p&gt;I do side projects like this when I need to step away from whatever I'm shipping day-to-day. They're not pitches. They don't need a thesis. They're more like the way some people fix a bike on a Saturday: sit down, follow whatever's pulling on your curiosity, and at the end you have a thing that didn't exist before. That's the whole reason.&lt;/p&gt;

&lt;p&gt;So this was that. A few evenings poking at &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt; and &lt;a href="https://docs.rs/russh" rel="noopener noreferrer"&gt;russh&lt;/a&gt; to see what it would take to make &lt;code&gt;ssh agnelnieves.sh&lt;/code&gt; actually work. Turns out: less than I expected. The hardest parts were the ones I didn't see coming, which is what makes any of this fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;One binary. Two modes. One render path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            ┌─────────────────────────────┐
            │ agnel (single .exe) │
            └──────────────┬──────────────┘
                           │
            ┌──────────────┴──────────────┐
            │ │
       agnel (no flag) agnel --serve
       Local TUI SSH server (Fly.io)
       crossterm stdin russh + custom backend
            │ │
            └──────────┬──────────────────┘
                       │
                src/render.rs ← shared
                       │
                Per-session App state
                       │
                Live fetches from agnelnieves.com APIs

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TUI:&lt;/strong&gt; &lt;a href="https://ratatui.rs" rel="noopener noreferrer"&gt;ratatui&lt;/a&gt; draws everything: header, ticker, ASCII banner, projects list, blog reader. The render function lives in &lt;code&gt;src/render.rs&lt;/code&gt; and is identical whether the binary is running on your laptop or whether you're seeing it over SSH.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State machine:&lt;/strong&gt; &lt;code&gt;App&lt;/code&gt; in &lt;code&gt;src/app.rs&lt;/code&gt; owns screen, scroll position, list selection, ticker animation, and async-fetch results funneled through an &lt;code&gt;mpsc::Receiver&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; zero content is bundled. The CLI fetches live from &lt;code&gt;/feed.json&lt;/code&gt;, &lt;code&gt;/api/blog/[slug]/raw&lt;/code&gt;, &lt;code&gt;/api/projects&lt;/code&gt;, and &lt;code&gt;/api/site.json&lt;/code&gt; on this site, cached for an hour at &lt;code&gt;~/.agnel/cache/&lt;/code&gt;. So a new blog post here shows up in your terminal immediately without redeploying anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH server:&lt;/strong&gt; &lt;a href="https://docs.rs/russh" rel="noopener noreferrer"&gt;russh 0.60&lt;/a&gt; in &lt;code&gt;src/serve.rs&lt;/code&gt;. One tokio task per session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The decision that made it easy: don't fork a PTY
&lt;/h2&gt;

&lt;p&gt;Most "expose a TUI over SSH" guides suggest spawning a pseudo-terminal and &lt;code&gt;exec&lt;/code&gt;-ing the TUI binary inside it. That's how &lt;code&gt;mosh&lt;/code&gt;, &lt;code&gt;tmux&lt;/code&gt;, and Go's &lt;a href="https://charm.sh/blog/wish/" rel="noopener noreferrer"&gt;&lt;code&gt;wish&lt;/code&gt;&lt;/a&gt; library (the one terminal.shop uses) handle things. It works, but it's heavier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adds a libc dependency (&lt;code&gt;forkpty&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Forks a process per connection (more memory, slower spawn)&lt;/li&gt;
&lt;li&gt;Hard to share state between the SSH layer and the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Russh's &lt;code&gt;ratatui_app&lt;/code&gt; example showed a cleaner path: implement a custom &lt;code&gt;std::io::Write&lt;/code&gt; backend that funnels bytes through russh's &lt;code&gt;Handle::data(channel, bytes)&lt;/code&gt; API. Ratatui doesn't know it's writing to an SSH channel instead of stdout. No fork, no PTY, just bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TerminalSink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UnboundedSender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;TerminalSink&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&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;self&lt;/span&gt;&lt;span class="py"&gt;.sink&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.sink&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.sender&lt;/span&gt;&lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.sink&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A background tokio task drains the channel and pushes bytes back to the client through russh. Each session gets its own &lt;code&gt;App&lt;/code&gt; and its own &lt;code&gt;Terminal&amp;lt;CrosstermBackend&amp;lt;TerminalSink&amp;gt;&amp;gt;&lt;/code&gt;. The lock graph stays tiny: an &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;SessionState&amp;gt;&amp;gt;&lt;/code&gt; per session, plus a &lt;code&gt;HashMap&lt;/code&gt; of all sessions for cleanup.&lt;/p&gt;

&lt;p&gt;Inputs go the other direction. Russh hands raw bytes to my &lt;code&gt;data()&lt;/code&gt; callback, and a small parser turns them into the same &lt;code&gt;crossterm::event::KeyEvent&lt;/code&gt; values the local TUI uses. That meant zero changes to &lt;code&gt;App::handle_key&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;parse_keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeyEvent&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;// 0x03 → Ctrl+C, 0x1b[A → Up, 0x1b alone → Esc,&lt;/span&gt;
    &lt;span class="c1"&gt;// printable ASCII → Char(b), etc.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Window resize hooks into the same place. &lt;code&gt;window_change_request&lt;/code&gt; calls &lt;code&gt;terminal.resize(rect)&lt;/code&gt;, and ratatui redraws on the next 50 ms tick.&lt;/p&gt;

&lt;p&gt;The result: a single binary you can drop on a VPS, in a container, or even into a Lambda-style worker, and it just serves the TUI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned about hosting
&lt;/h2&gt;

&lt;p&gt;A few things I didn't expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Fly trial orgs block dedicated IPv4, and SSH needs IPv4.&lt;/strong&gt; For HTTP, Fly's edge gives you a shared anycast IPv4 for free. For raw TCP on port 22, you need a dedicated v4 (~$2/mo), and trial orgs can't allocate one. Pure-IPv6 isn't a real option, either: most US residential ISPs still don't have native IPv6 SSH paths. Adding a card was the unlock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The SSH host key has to persist.&lt;/strong&gt; If the container generates a fresh Ed25519 key on every boot, every returning visitor gets &lt;code&gt;REMOTE HOST IDENTIFICATION HAS CHANGED&lt;/code&gt; warnings after the next deploy. The fix is a 1 GB Fly volume mounted at &lt;code&gt;/data&lt;/code&gt;, with &lt;code&gt;--host-key /data/ssh_host_key&lt;/code&gt;. First boot generates and writes the key; every boot after just reads it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Trial machines auto-stop after 5 minutes.&lt;/strong&gt; That's how the Fly free tier nudges you toward billing. Once you're paying, set &lt;code&gt;auto_stop_machines = "off"&lt;/code&gt; and &lt;code&gt;min_machines_running = 1&lt;/code&gt; if you want a long-running service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;fly proxy&lt;/code&gt; is fine for HTTP, weird for raw SSH.&lt;/strong&gt; I burned half an hour on &lt;code&gt;Connection reset by peer&lt;/code&gt; through &lt;code&gt;fly proxy 12222:2222&lt;/code&gt; before realizing I should just &lt;code&gt;bash /dev/tcp/127.0.0.1/2222&lt;/code&gt; from inside the machine via &lt;code&gt;flyctl ssh console&lt;/code&gt;. The SSH server was healthy the whole time; the proxy path was the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  ANSI Shadow beats slant
&lt;/h2&gt;

&lt;p&gt;The first banner I shipped used figlet's &lt;code&gt;slant&lt;/code&gt; font. It looks fine in a docs file. At terminal width with full-block rendering, it falls apart into disconnected diagonals.&lt;/p&gt;

&lt;p&gt;Swapped to &lt;a href="https://patorjk.com/software/taag/#p=display&amp;amp;f=ANSI%20Shadow&amp;amp;t=AGNEL%20NIEVES" rel="noopener noreferrer"&gt;ANSI Shadow&lt;/a&gt;, the same boxy block font terminal.shop and &lt;a href="https://charm.sh" rel="noopener noreferrer"&gt;charm.sh&lt;/a&gt; use. Six rows tall, about 93 columns wide, reads instantly. Probably the kind of thing I'd have gotten right the first time if I'd looked at more references before opening figlet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visitor #N
&lt;/h2&gt;

&lt;p&gt;One fun thing I added. Look at the bottom-left of the footer when you connect: it says &lt;code&gt;visitor #N&lt;/code&gt;. First SSH session ever was #1, second was #2, and so on. The count lives in a tiny text file on the Fly volume next to the host key, increments once per &lt;code&gt;channel_open_session&lt;/code&gt;, and persists across deploys (host-key lesson applied).&lt;/p&gt;

&lt;p&gt;Implementation is about thirty lines. An &lt;code&gt;AtomicU64&lt;/code&gt; loaded from the file at startup, a &lt;code&gt;spawn_blocking&lt;/code&gt; write after each &lt;code&gt;fetch_add&lt;/code&gt;. The file is just a base-10 number with no header, so &lt;code&gt;cat /data/visitor_count&lt;/code&gt; is the dashboard.&lt;/p&gt;

&lt;p&gt;There's no leaderboard. No real meaning to the number. But watching it tick up after shipping is the moment the whole side project starts feeling worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tech&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TUI&lt;/td&gt;
&lt;td&gt;ratatui 0.29, crossterm 0.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;russh 0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP client&lt;/td&gt;
&lt;td&gt;ureq 2 (sync, plenty for this)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async runtime&lt;/td&gt;
&lt;td&gt;tokio 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI args&lt;/td&gt;
&lt;td&gt;clap 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;Fly.io (&lt;code&gt;iad&lt;/code&gt;, shared-cpu-1x, 256 MB, 1 GB volume)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;td&gt;Vercel Domains (A + AAAA on the apex)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary size&lt;/td&gt;
&lt;td&gt;~3 MB release (macOS arm64), 4.3 MB (Linux x86_64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image size&lt;/td&gt;
&lt;td&gt;28 MB (Debian bookworm-slim runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;$2/mo for the dedicated IPv4 + free tier for the rest&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pretty errors and loading states.&lt;/strong&gt; Today they say &lt;code&gt;Loading…&lt;/code&gt; which is functional but boring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project deep-links.&lt;/strong&gt; I render project metadata but not the original write-ups. Pulling MDX through the API would let me ship long-form case studies in-terminal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform release binaries.&lt;/strong&gt; The current install path is &lt;code&gt;cargo install --path cli/&lt;/code&gt;. Homebrew tap + release artifacts for macOS and Linux is a Saturday morning.&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh agnelnieves.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to build your own version, the &lt;a href="https://agnelnieves.com/guides/ssh-terminal-portfolio.md" rel="noopener noreferrer"&gt;guide&lt;/a&gt; has the full setup (Rust + ratatui + russh + Fly.io), with a copy-paste prompt at the top so you can hand it to your AI agent of choice. If you ship one, &lt;a href="https://agnelnieves.com/follow" rel="noopener noreferrer"&gt;let me know&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by &lt;a href="https://agnelnieves.com/about" rel="noopener noreferrer"&gt;Agnel Nieves&lt;/a&gt;, a design engineer with 15+ years across product, design systems, and crypto. More writing on &lt;a href="https://agnelnieves.com/blog" rel="noopener noreferrer"&gt;the blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ratatui</category>
      <category>ssh</category>
      <category>flyio</category>
    </item>
    <item>
      <title>Optimizing Your Website for AI Agents and LLMs</title>
      <dc:creator>Agnel Nieves</dc:creator>
      <pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/agnelnieves/optimizing-your-website-for-ai-agents-and-llms-1183</link>
      <guid>https://dev.to/agnelnieves/optimizing-your-website-for-ai-agents-and-llms-1183</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll32v2kt40lfm96gnqv0.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%2Fll32v2kt40lfm96gnqv0.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your website has two audiences now. Humans, obviously. But also AI agents — LLMs that crawl, summarize, cite, and recommend your content to millions of people. If your site isn't optimized for both, you're leaving visibility on the table.&lt;/p&gt;

&lt;p&gt;I just finished optimizing &lt;a href="https://agnelnieves.com" rel="noopener noreferrer"&gt;my personal site&lt;/a&gt; for AI consumption, and the process revealed something interesting: most of what makes a site good for AI also makes it better for humans. Clear structure, machine-readable content, and explicit metadata benefit everyone.&lt;/p&gt;

&lt;p&gt;Here's what I did and why it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are AI Agents Actually Doing with Your Site?
&lt;/h2&gt;

&lt;p&gt;When someone asks ChatGPT, Claude, Perplexity, or Google's AI Overview a question, those systems don't just generate answers from training data. Increasingly, they fetch and cite live web content. Your site might get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Crawled for training data&lt;/strong&gt; by bots like GPTBot, ClaudeBot, and Google-Extended&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetched at query time&lt;/strong&gt; by Perplexity, ChatGPT browsing, and similar agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cited as a source&lt;/strong&gt; in AI-generated responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summarized in featured snippets&lt;/strong&gt; and AI overviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigated by autonomous agents&lt;/strong&gt; that interact with your APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these has different needs, but they all benefit from the same foundation: structured, discoverable, machine-readable content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The llms.txt Standard
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://llmstxt.org" rel="noopener noreferrer"&gt;llms.txt spec&lt;/a&gt; is the equivalent of &lt;code&gt;robots.txt&lt;/code&gt; for AI agents. While &lt;code&gt;robots.txt&lt;/code&gt; tells crawlers what they &lt;em&gt;can&lt;/em&gt; access, &lt;code&gt;llms.txt&lt;/code&gt; tells them what your site &lt;em&gt;is&lt;/em&gt; — a structured markdown index served at your domain root.&lt;/p&gt;

&lt;p&gt;The format is simple:&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;# Your Name or Site&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; A one-line summary of what this site is.&lt;/span&gt;

A longer description paragraph.

&lt;span class="gu"&gt;## Section Name&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Link Title&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Description of what's at this link

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I implemented two variants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/llms.txt&lt;/code&gt;&lt;/strong&gt; — the index. A table of contents with links to all pages, blog posts, projects, social profiles, and feeds. Think of it as a menu for AI agents to browse selectively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/llms-full.txt&lt;/code&gt;&lt;/strong&gt; — the full dump. Every blog post's complete markdown content, every project description, biographical context. For agents that want to load everything into context at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are served as &lt;code&gt;text/plain&lt;/code&gt; with markdown formatting. Both are generated dynamically from the same data sources that power the site, so they never go stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inline LLM Instructions in HTML
&lt;/h2&gt;

&lt;p&gt;This one comes from a &lt;a href="https://vercel.com/blog/a-proposal-for-inline-llm-instructions-in-html" rel="noopener noreferrer"&gt;Vercel proposal&lt;/a&gt; and it's clever: embed AI-readable instructions directly in your page's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; using a script tag browsers ignore.&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;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/llms.txt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;Site&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt;

&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;personal&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="k"&gt;of&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;a&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="nx"&gt;based&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;

&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="nx"&gt;Site&lt;/span&gt; &lt;span class="nx"&gt;Structure&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sr"&gt;/ — Home: Descriptio&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sr"&gt;/blog — Blog: Descriptio&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sr"&gt;/about — About: Descriptio&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;
&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="nx"&gt;Facts&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="nx"&gt;Your&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;Role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;Role&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Specialties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Thing&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Thing&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Thing&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsers skip &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags with unknown types. LLMs process them. It's a zero-cost way to give every page on your site a machine-readable context block. I added one to my root layout that describes who I am, the site structure, and where to find machine-readable content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured Data That AI Engines Actually Use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://json-ld.org/" rel="noopener noreferrer"&gt;JSON-LD&lt;/a&gt; structured data has always been important for Google. It's now equally important for AI engines. When an LLM encounters schema.org markup, it understands the &lt;em&gt;semantics&lt;/em&gt; of your content — not just the text, but what the text represents.&lt;/p&gt;

&lt;p&gt;I already had structured data for my blog posts (&lt;code&gt;BlogPosting&lt;/code&gt; schema with breadcrumbs). What I added was &lt;code&gt;CreativeWork&lt;/code&gt; schema for my &lt;a href="https://agnelnieves.com/work" rel="noopener noreferrer"&gt;portfolio projects&lt;/a&gt;, giving each project a machine-readable identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CreativeWork"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Project Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What this project is"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://project-url.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"creator"&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;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Person"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more schema types you cover, the more AI engines can understand and cite your work with proper attribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Machine-Readable Feeds
&lt;/h2&gt;

&lt;p&gt;RSS is great, but it's XML — not the most natural format for AI agents to parse. I added a &lt;a href="https://www.jsonfeed.org/" rel="noopener noreferrer"&gt;JSON Feed&lt;/a&gt; endpoint alongside my existing RSS feed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/feed.xml&lt;/code&gt;&lt;/strong&gt; — RSS 2.0 for traditional feed readers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/feed.json&lt;/code&gt;&lt;/strong&gt; — JSON Feed 1.1 for programmatic consumption&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JSON Feed is cleaner for AI agents to parse and reference. Both are registered in the site's metadata so they're auto-discoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making robots.txt AI-Aware
&lt;/h2&gt;

&lt;p&gt;Most sites already have a &lt;code&gt;robots.txt&lt;/code&gt;. The key addition is explicitly allowing AI crawlers and pointing them to your &lt;code&gt;llms.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;GPTBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;ClaudeBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;PerplexityBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /

&lt;span class="n"&gt;Sitemap&lt;/span&gt;: &lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;yoursite&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;/&lt;span class="n"&gt;sitemap&lt;/span&gt;.&lt;span class="n"&gt;xml&lt;/span&gt;

&lt;span class="c"&gt;# AI/LLM Content
# llms.txt: https://yoursite.com/llms.txt
# llms-full.txt: https://yoursite.com/llms-full.txt
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many sites block AI crawlers by default. If you &lt;em&gt;want&lt;/em&gt; your content cited and discovered by AI, explicitly allow the major bots: &lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ChatGPT-User&lt;/code&gt;, &lt;code&gt;Google-Extended&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, &lt;code&gt;anthropic-ai&lt;/code&gt;, &lt;code&gt;PerplexityBot&lt;/code&gt;, &lt;code&gt;Applebot-Extended&lt;/code&gt;, &lt;code&gt;Bytespider&lt;/code&gt;, and &lt;code&gt;cohere-ai&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Creators
&lt;/h2&gt;

&lt;p&gt;As a design engineer with 15+ years of building products, I've watched SEO evolve from keyword stuffing to semantic web to AI-native discovery. We're at an inflection point. The sites that get cited by AI aren't necessarily the ones with the best domain authority — they're the ones with the clearest, most structured, most machine-readable content.&lt;/p&gt;

&lt;p&gt;This is especially important for personal sites and portfolios. When someone asks an AI "who are the best design engineers in Miami?" or "what's a good article about design tokens?", you want your site to be citable. That requires more than good content — it requires content that AI can &lt;em&gt;find&lt;/em&gt;, &lt;em&gt;understand&lt;/em&gt;, and &lt;em&gt;attribute&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Full Stack of AI Optimization
&lt;/h2&gt;

&lt;p&gt;Here's the complete checklist of what I now have in place:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;robots.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explicitly allow AI bots&lt;/td&gt;
&lt;td&gt;Let them crawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sitemap.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dynamic sitemap with all content&lt;/td&gt;
&lt;td&gt;Let them discover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Markdown index of the site&lt;/td&gt;
&lt;td&gt;Let them understand structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full content in one file&lt;/td&gt;
&lt;td&gt;Let them ingest everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Page-level LLM instructions&lt;/td&gt;
&lt;td&gt;Let them understand context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON-LD&lt;/td&gt;
&lt;td&gt;Structured data on every page&lt;/td&gt;
&lt;td&gt;Let them understand semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSS + JSON Feed&lt;/td&gt;
&lt;td&gt;Machine-readable content feeds&lt;/td&gt;
&lt;td&gt;Let them subscribe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta tags&lt;/td&gt;
&lt;td&gt;OpenGraph, Twitter, canonical&lt;/td&gt;
&lt;td&gt;Let them cite accurately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these changes affect how the site looks or feels for human visitors. They're invisible additions that make the site dramatically more useful for AI.&lt;/p&gt;

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

&lt;p&gt;The AI web is evolving fast. Standards like &lt;code&gt;llms.txt&lt;/code&gt; are still emerging, and new patterns will appear. But the fundamentals won't change: structure your content clearly, make it discoverable, and give machines the metadata they need to understand it.&lt;/p&gt;

&lt;p&gt;If you want to replicate this setup, I've published a &lt;a href="https://agnelnieves.com/guides/ai-optimization-guide.md" rel="noopener noreferrer"&gt;full implementation guide&lt;/a&gt; with code examples for Next.js. The approach works for any framework — the concepts are universal.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Building something and want to talk AI optimization? &lt;a href="https://agnelnieves.com/follow" rel="noopener noreferrer"&gt;Let's connect&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>webdev</category>
      <category>llms</category>
    </item>
    <item>
      <title>We Found a Hidden Pet System in Claude Code's Leaked Source and Shipped It Overnight</title>
      <dc:creator>Agnel Nieves</dc:creator>
      <pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/agnelnieves/we-found-a-hidden-pet-system-in-claude-codes-leaked-source-and-shipped-it-overnight-23fk</link>
      <guid>https://dev.to/agnelnieves/we-found-a-hidden-pet-system-in-claude-codes-leaked-source-and-shipped-it-overnight-23fk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsft85sz04tvf7vboxag8.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%2Fsft85sz04tvf7vboxag8.png" alt="Screenshot of claudebuddy" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On March 30th, 2026, Anthropic accidentally shipped a 59.8 MB source map file inside their Claude Code npm package. Within hours, the entire 512,000-line TypeScript codebase was mirrored across GitHub and picked apart by thousands of developers. Buried inside that code, alongside feature flags for autonomous agents and undercover commit modes, was something nobody expected: a fully built virtual pet system called &lt;strong&gt;Buddy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My co-founder &lt;a href="https://x.com/peronif5" rel="noopener noreferrer"&gt;peroni&lt;/a&gt; and I did the only sensible thing. We shipped it.&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;We all know what happened yday with Claude Code. Buried in the source: a hidden pet system called "Buddy." Every user gets a unique pixel creature based on their ID. Deterministic, same hash, same buddy, every time. So &lt;a href="https://twitter.com/peronif5?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;@peronif5&lt;/a&gt; and I did the most sensible thing... Shipped it.… &lt;a href="https://t.co/YE4ZSEIXq7" rel="noopener noreferrer"&gt;pic.twitter.com/YE4ZSEIXq7&lt;/a&gt;&lt;/p&gt;— Agnel (🇵🇷) (&lt;a class="mentioned-user" href="https://dev.to/agnelnieves"&gt;@agnelnieves&lt;/a&gt;) &lt;a href="https://twitter.com/agnelnieves/status/2039311800005525807?ref_src=twsrc%5Etfw" rel="noopener noreferrer"&gt;April 1, 2026&lt;/a&gt;
&lt;/blockquote&gt;



&lt;h2&gt;
  
  
  What Happened with the Claude Code Leak
&lt;/h2&gt;

&lt;p&gt;If you missed it, here's the short version. Chaofan Shou (&lt;a href="https://x.com/Fried_rice" rel="noopener noreferrer"&gt;@Fried_rice&lt;/a&gt;) noticed that version 2.1.88 of the &lt;code&gt;@anthropic-ai/claude-code&lt;/code&gt; package on npm included an unminified source map — &lt;code&gt;cli.js.map&lt;/code&gt; — containing the full, readable TypeScript source. By 4:23 AM ET, it was public. By noon, the internet had catalogued every hidden feature, internal codename, and unreleased capability Anthropic had been quietly building.&lt;/p&gt;

&lt;p&gt;Among the bigger discoveries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;KAIROS&lt;/strong&gt; — an always-on daemon mode that lets Claude Code operate as a persistent background agent, watching, logging, and acting without waiting for user input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undercover mode&lt;/strong&gt; — auto-activated for Anthropic employees on public repos, stripping AI attribution from commits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;44 feature flags&lt;/strong&gt; covering unreleased functionality&lt;/li&gt;
&lt;li&gt;And tucked away, a complete companion pet system called &lt;strong&gt;Buddy&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Buddy System: A Pixel Pet for Every User
&lt;/h2&gt;

&lt;p&gt;The Buddy system was fully implemented. Every Claude Code user was supposed to get a unique pixel creature generated deterministically from their user ID. Same hash, same buddy, every time. The code included 18 species across rarity tiers, stat generation, personality descriptions — the whole gacha experience, just waiting to be turned on.&lt;/p&gt;

&lt;p&gt;It was the kind of detail that makes you smile. In a tool built for productivity and code generation, someone at Anthropic took the time to build a pet system. A little pixel friend that lives in your terminal. That's the kind of craft and whimsy that makes developer tools memorable.&lt;/p&gt;

&lt;p&gt;The moment I saw it, I knew what we had to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Discovery to Deploy in a Day
&lt;/h2&gt;

&lt;p&gt;Peroni and I have a rhythm. We spot something interesting, we build. No planning committee, no Jira tickets, no "let's circle back Monday." As a design engineer with 15+ years of shipping products, I've learned that the best side projects are the ones you can't &lt;em&gt;not&lt;/em&gt; build. This was one of those.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://www.claudebuddy.me/" rel="noopener noreferrer"&gt;Claude Buddy&lt;/a&gt; — a web app that lets anyone generate their own pixel companion. Type your name or your Claude Code user ID, and watch it draw your buddy pixel by pixel, complete with retro CRT animations and pop sounds.&lt;/p&gt;

&lt;p&gt;Here's what we shipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 unique pixel art species&lt;/strong&gt; across 5 rarity tiers — from common Blobbits to the legendary Nebulynx (3% chance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic generation&lt;/strong&gt; — same name always produces the same buddy, making them feel like &lt;em&gt;yours&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shiny variants&lt;/strong&gt; with a 5% drop rate, because of course&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buddy stats&lt;/strong&gt; — Vibe, Chaos, Focus, and Luck, each randomly rolled but consistent to your hash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-command terminal install&lt;/strong&gt; — &lt;code&gt;curl&lt;/code&gt; a script and your buddy appears in your Claude Code statusline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social sharing&lt;/strong&gt; — download as PNG, share via URL, generate QR codes, post to X/LinkedIn with pre-populated text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing runs on Next.js 15, renders sprites on HTML5 Canvas, and uses a Mulberry32 PRNG seeded by a DJB2 hash of your input. No backend, no database, no authentication. Pure deterministic fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build This?
&lt;/h2&gt;

&lt;p&gt;Partly because it's fun. Partly because of &lt;a href="https://dev.to/agnelnieves/the-ai-native-design-gap-from-static-to-dynamic-experiences-2mb4"&gt;how I approach creative work&lt;/a&gt; — I try to ship a hackathon-style project every quarter to stay sharp and experiment with new patterns. But mostly because I think the Buddy system represents something important about how we relate to our tools.&lt;/p&gt;

&lt;p&gt;Developer tools don't have to be purely utilitarian. The best ones have personality. They reward curiosity. They make you &lt;em&gt;want&lt;/em&gt; to open the terminal. A pixel pet that lives next to your cursor won't make you a better programmer, but it might make the work feel a little less solitary.&lt;/p&gt;

&lt;p&gt;Anthropic clearly felt the same way — they built the whole thing, ready to ship. We just opened the door a little early.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Generation Algorithm Works
&lt;/h2&gt;

&lt;p&gt;For the technically curious, the generation is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the input string (name or user ID), lowercase and trim it&lt;/li&gt;
&lt;li&gt;Salt it with a fixed string to avoid collisions&lt;/li&gt;
&lt;li&gt;Run a DJB2 hash to convert it to a numeric seed&lt;/li&gt;
&lt;li&gt;Feed that seed into a Mulberry32 PRNG&lt;/li&gt;
&lt;li&gt;Roll for species (weighted by rarity tier probabilities)&lt;/li&gt;
&lt;li&gt;Roll for shiny status (5% chance)&lt;/li&gt;
&lt;li&gt;Generate four stats between 1 and 99&lt;/li&gt;
&lt;li&gt;Select a soul description from the species pool&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same input, same seed, same rolls, same buddy. Every time. No server involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Open Source
&lt;/h2&gt;

&lt;p&gt;The entire project is &lt;a href="https://github.com/basement-browser/claude-buddy" rel="noopener noreferrer"&gt;open source on GitHub&lt;/a&gt;. Built by the &lt;a href="https://basementbrowser.com" rel="noopener noreferrer"&gt;Basement&lt;/a&gt; team. Fork it, remix it, hatch your own buddy.&lt;/p&gt;

&lt;p&gt;If you want to see what came out of this, check the &lt;a href="https://x.com/agnelnieves/status/2039311800005525807" rel="noopener noreferrer"&gt;thread on X&lt;/a&gt; where we announced it. And if you're building with Claude Code, maybe your buddy is already waiting — just type your name and find out.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have thoughts on this or want to collaborate on something weird? &lt;a href="https://dev.to/connect"&gt;Let's connect&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>opensource</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>The AI-Native Design Gap: From Static to Dynamic Experiences</title>
      <dc:creator>Agnel Nieves</dc:creator>
      <pubDate>Thu, 16 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/agnelnieves/the-ai-native-design-gap-from-static-to-dynamic-experiences-2mb4</link>
      <guid>https://dev.to/agnelnieves/the-ai-native-design-gap-from-static-to-dynamic-experiences-2mb4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fto3bqt7mdj6ozj9al3x8.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%2Fto3bqt7mdj6ozj9al3x8.png" alt="Abstract lines" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's something I see all the time: teams spend weeks perfecting static mockups in Figma, present them with confidence, and then watch as stakeholders nod politely but don't quite get it. Why? Well, we're designing static artifacts for dynamic experiences.&lt;/p&gt;

&lt;p&gt;Every scroll, swipe, and tap in a real product triggers visual feedback. It's how users actually understand what's happening. Yet we keep pitching ideas as if they exist in freeze-frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-Native Design Challenge
&lt;/h2&gt;

&lt;p&gt;This gap matters more than ever, especially if you're working on AI-native products. Whether it's OpenAI's ChatGPT, Perplexity's search interface, or Claude's conversations—these products are rewriting the rules of digital interaction. They're introducing entirely new patterns: streaming responses, conversational flows, adaptive interfaces that feel less like traditional apps and more like living dialogues. Even more so if the interactions include voice motion which is supposed to react to the user's voice.&lt;/p&gt;

&lt;p&gt;You can't capture that in a static frame. You have to show it in motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Interaction Design Is an Unlocked Skill
&lt;/h2&gt;

&lt;p&gt;Here's the thing about motion and interaction design: it's a toolkit you unlock through experimentation, not strictly tied to theory or studies. You learn it by thinking about physics, natural movement, user intent—concepts you absorb from using dozens of different apps and noticing what feels right. Think about it, try to explain a motion ui pattern to someone, try to explain the core concept of what you have in your mind. It's difficult… It's open to interpretation of the person to whom you're explaining it to, and limited by their imagination (which is highly likely different from yours).&lt;/p&gt;

&lt;p&gt;This creates a knowledge gap. Most designers haven't had the chance to experiment enough with motion to build that intuition. But here's what I've learned: even designing interactions frame-by-frame, storyboard-style like old Disney animations, can be enough to transform how you communicate ideas. It forces you to think through every transition, every state change, every moment of feedback.&lt;/p&gt;

&lt;p&gt;And it doesn't just help you—it helps engineering understand exactly what you're trying to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Is Taking the Spotlight Again
&lt;/h2&gt;

&lt;p&gt;We're in an exciting moment. With AI tools and no-code platforms, pretty much anyone can spin up a functional prototype. The technical barrier to entry has lowered significantly.&lt;/p&gt;

&lt;p&gt;What does that mean? Design becomes the differentiator. When everyone can build something that works, the products that win are the ones that feel incredible to use. Motion, polish, and thoughtful interaction patterns aren't nice-to-haves anymore—they're what separates successful products from forgettable ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Approach: Build to Learn
&lt;/h2&gt;

&lt;p&gt;I try to ship a hackathon-style project every quarter. Not because I need more side projects, but because it's the only way to stay ahead and fresh. New patterns emerge constantly. AI products iterate at light speed. Existing products unveil interactions you never even considered.&lt;/p&gt;

&lt;p&gt;The only way to keep your intuition sharp is to experiment relentlessly with what's out there.&lt;/p&gt;

&lt;p&gt;This comes from personal experience. My background has helped me a lot in this area—I started as a designer, shifted to software engineering, and eventually came back to design. That engineering foundation changed everything. I can quickly prototype motion patterns directly in code (often faster than in design tools), play with them until something clicks, then bring those learnings back to Figma to polish everything together.&lt;/p&gt;

&lt;p&gt;This workflow might sound backwards, but to me, it makes sense and it just works: code/define the motion first, design and polish last. Code gives you the freedom to experiment rapidly. Spin up prototypes quickly, and gather feedback. Design tools give you the precision to perfect it.&lt;/p&gt;

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

&lt;p&gt;If you're trying to sell an idea—whether to stakeholders, users, or investors—don't stop at static screens. Invest time in showing how it moves. Show how it responds. Show how it feels.&lt;/p&gt;

&lt;p&gt;You don't need to be an engineer to do this. Start simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sketch interaction sequences frame-by-frame&lt;/li&gt;
&lt;li&gt;Use Figma's prototyping features to simulate key transitions&lt;/li&gt;
&lt;li&gt;Try tools like ProtoPie, Lottie Lab for more complex motion&lt;/li&gt;
&lt;li&gt;Or, if you're comfortable with code, prototype directly in React or HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The medium matters less than the commitment: design for motion, not just for pixels.&lt;/p&gt;

&lt;p&gt;Because in an era where AI is democratizing creation, the products that win won't just work—they'll feel magical. And you can't explain magic in a static mockup.&lt;/p&gt;

</description>
      <category>design</category>
      <category>motion</category>
      <category>ai</category>
      <category>prototyping</category>
    </item>
  </channel>
</rss>
