<?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: Varshith V Hegde</title>
    <description>The latest articles on DEV Community by Varshith V Hegde (@varshithvhegde).</description>
    <link>https://dev.to/varshithvhegde</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885064%2F4ab304f4-a3f3-409c-8217-9ce130e57c18.jpeg</url>
      <title>DEV Community: Varshith V Hegde</title>
      <link>https://dev.to/varshithvhegde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/varshithvhegde"/>
    <language>en</language>
    <item>
      <title>PawBot - An AI Desktop Puppy That Eats Your Files 🐾</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:39:22 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/pawbot-an-ai-desktop-puppy-that-eats-your-files-301a</link>
      <guid>https://dev.to/varshithvhegde/pawbot-an-ai-desktop-puppy-that-eats-your-files-301a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Remember &lt;strong&gt;Neko the cat&lt;/strong&gt; and those &lt;strong&gt;Tamagotchi desktop pets&lt;/strong&gt; from the 90s? I rebuilt that magic for 2026 — except your pet is a golden retriever puppy powered by an LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PawBot&lt;/strong&gt; is a Chrome extension + browser demo where a cute &lt;strong&gt;pixel puppy&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🐕 &lt;strong&gt;Roams&lt;/strong&gt; your screen like a classic desktop pet&lt;/li&gt;
&lt;li&gt;🏃 &lt;strong&gt;Chases your cursor&lt;/strong&gt; when you get close&lt;/li&gt;
&lt;li&gt;🍖 &lt;strong&gt;Eats anything you drop&lt;/strong&gt; — PDFs, code files, emoji, pasted text&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Barks back&lt;/strong&gt; with short, mood-aware responses via &lt;strong&gt;Google Gemini&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;😴 &lt;strong&gt;Falls asleep&lt;/strong&gt; if you ignore it too long (with Zzz particles!)&lt;/li&gt;
&lt;li&gt;❤️ &lt;strong&gt;Reacts to pets&lt;/strong&gt; — click to pet, double-click to wave&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feed it a &lt;code&gt;.pdf&lt;/code&gt; and it might say &lt;em&gt;"Mmm, crunchy homework!"&lt;/em&gt; Ignore it for 40 seconds and it curls up with sad puppy eyes.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvmdl33ixtvsk2j9b790s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvmdl33ixtvsk2j9b790s.png" alt="PawBot idle sprite" width="64" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhacl6zvdsgecv9sxvdjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhacl6zvdsgecv9sxvdjq.png" alt="PawBot eating" width="64" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wh1202cydfw1enb9m3n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7wh1202cydfw1enb9m3n.png" alt="PawBot barking" width="64" height="64"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Full walkthrough on YouTube:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/31XDjzV8mgU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick try locally:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Varshithvhegde/pawbot.git
&lt;span class="nb"&gt;cd &lt;/span&gt;pawbot/web
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8080
&lt;span class="c"&gt;# → http://localhost:8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Or load the Chrome extension: clone the repo → &lt;code&gt;./scripts/sync-extension.sh&lt;/code&gt; → Load unpacked → select the &lt;code&gt;extension/&lt;/code&gt; folder.&lt;/p&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Varshithvhegde" rel="noopener noreferrer"&gt;
        Varshithvhegde
      &lt;/a&gt; / &lt;a href="https://github.com/Varshithvhegde/pawbot" rel="noopener noreferrer"&gt;
        pawbot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI desktop puppy Chrome extension — pixel pet that eats your files and barks with Google Gemini
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PawBot 🐾&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;An AI desktop puppy that lives in your browser.&lt;/strong&gt; A Tamagotchi/Neko-style pixel pet that roams your screen, chases your cursor, eats files &amp;amp; emoji you drop, and barks mood-aware responses powered by &lt;strong&gt;Google Gemini&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Built for the &lt;a href="https://dev.to/challenge" rel="nofollow"&gt;DEV Weekend Challenge: Dog Days Edition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Varshithvhegde/pawbot/assets/sprites/idle_0.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVarshithvhegde%2Fpawbot%2FHEAD%2Fassets%2Fsprites%2Fidle_0.png" alt="PawBot pixel puppy" width="128"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;🎬 &lt;strong&gt;Watch on YouTube&lt;/strong&gt; · 📝 &lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/pawbot/docs/DEV_SUBMISSION.md" rel="noopener noreferrer"&gt;DEV submission&lt;/a&gt;&lt;/strong&gt; · 🔗 &lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/pawbot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Feature&lt;/th&gt;

&lt;th&gt;Description&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;🐕 &lt;strong&gt;Pixel puppy&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Full sprite animations — idle, walk, run, eat, sleep, bark, wave&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;🍖 &lt;strong&gt;Feed anything&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Drop files, emoji, or paste text — pup chases food then eats it&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;🏃 &lt;strong&gt;Chase mechanics&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Food falls from above → pup runs to it → 4-frame eat animation&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;💬 &lt;strong&gt;AI barks&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Google Gemini generates short mood-based responses&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;😴 &lt;strong&gt;Sleep mode&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Ignore pup 40s → falls asleep with Zzz particles&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;❤️ &lt;strong&gt;Pet &amp;amp; wave&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;Click to pet, double-click to wave paw&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;⌨️ &lt;strong&gt;Hotkeys&lt;/strong&gt;&lt;br&gt;
&lt;/td&gt;

&lt;td&gt;
&lt;br&gt;
&lt;code&gt;F&lt;/code&gt; throw treat · &lt;code&gt;B&lt;/code&gt; bark · &lt;code&gt;Esc&lt;/code&gt;&lt;br&gt;
&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Varshithvhegde/pawbot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Varshithvhegde/pawbot" rel="noopener noreferrer"&gt;github.com/Varshithvhegde/pawbot&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser tab
    │
    ├── Content script (bundled IIFE via esbuild)
    │       └── Shadow DOM canvas overlay (immune to page CSS)
    │
    ├── Pet engine (Canvas 2D state machine)
    │       idle → walk → run → eat → sleep → bark → wave
    │
    ├── Sprite loader (33 AI-generated pixel frames)
    │
    └── Bark engine → Google Gemini API
            mood + hunger + last meal → short bark text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technical decisions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&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;strong&gt;Vanilla JS + Canvas&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero build step for the web demo; screen-recording friendly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shadow DOM overlay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Page CSS can't hide the pup (learned this the hard way on AI Studio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;esbuild bundle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chrome content scripts can't use ES &lt;code&gt;import&lt;/code&gt; — single bundled file fixes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI sprite sheet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generated pixel art, auto-sliced with a Python script into 33 transparent PNGs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chase-then-eat flow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Food drops → pup runs → 4-frame eat animation → crumb particles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fallback barks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Works offline without an API key; Gemini enhances when configured&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Sprite animations (all 33 frames used!)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3r8mkb3u1dmjc33jr9or.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3r8mkb3u1dmjc33jr9or.png" alt="Walk animation frame" width="64" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzupeku9lwoy5enkr9f97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzupeku9lwoy5enkr9f97.png" alt="Run animation frame" width="64" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1nqsmnfdk96d0418r3kd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1nqsmnfdk96d0418r3kd.png" alt="Sleep animation frame" width="64" height="64"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4hkvga4ouw2nl3sb15c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4hkvga4ouw2nl3sb15c.png" alt="Happy mood" width="64" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Sprites&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Idle&lt;/td&gt;
&lt;td&gt;4-frame breathing loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Walk / Run&lt;/td&gt;
&lt;td&gt;Directional left &amp;amp; right sprites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eat&lt;/td&gt;
&lt;td&gt;4-frame chew cycle + flying food emoji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sleep&lt;/td&gt;
&lt;td&gt;Curled up + floating Zzz particles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mood&lt;/td&gt;
&lt;td&gt;Happy, sad, curious, excited overlays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bark&lt;/td&gt;
&lt;td&gt;Shows while AI speech bubble is active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wave&lt;/td&gt;
&lt;td&gt;Paw idle + 3-frame wave on pet/double-click&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Controls
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feed&lt;/td&gt;
&lt;td&gt;Drag &amp;amp; drop file or emoji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feed&lt;/td&gt;
&lt;td&gt;Paste text/emoji&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pet&lt;/td&gt;
&lt;td&gt;Click the puppy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wave&lt;/td&gt;
&lt;td&gt;Double-click&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throw treat&lt;/td&gt;
&lt;td&gt;Press &lt;code&gt;F&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bark&lt;/td&gt;
&lt;td&gt;Press &lt;code&gt;B&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Live &lt;strong&gt;hunger &amp;amp; happiness HUD&lt;/strong&gt; in the bottom-left corner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ Best Use of Google AI
&lt;/h3&gt;

&lt;p&gt;PawBot uses &lt;strong&gt;Google Gemini 2.0 Flash Lite&lt;/strong&gt; — not as a chatbot, but as a &lt;strong&gt;personality engine&lt;/strong&gt; for a desktop pet.&lt;/p&gt;

&lt;p&gt;Every bark sends real context to Gemini:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Context sent to Gemini&lt;/th&gt;
&lt;th&gt;Example bark&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fed a &lt;code&gt;.py&lt;/code&gt; file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"Just ate: main.py (file)"&lt;/code&gt;, mood: happy&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"Python for breakfast? Bold choice."&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ignored 40s+&lt;/td&gt;
&lt;td&gt;mood: lonely, ignoredMs: 45000&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"...hello? I saved you a spot."&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Poked&lt;/td&gt;
&lt;td&gt;happiness: 85, mood: excited&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"Hehe! Belly rubs detected!"&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hungry&lt;/td&gt;
&lt;td&gt;hunger: 82/100&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"Is that food? IS THAT FOOD?"&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Free API key from &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; → paste in extension popup. Falls back to canned barks if no key is set.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;🔊 ElevenLabs bark &lt;em&gt;sounds&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;🐩 Multiple dog breeds&lt;/li&gt;
&lt;li&gt;🦊 Firefox extension&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading! If PawBot made you smile, leave a 🐾 in the comments.&lt;/p&gt;

&lt;p&gt;Built for &lt;strong&gt;International Dog Day&lt;/strong&gt; 🐕 · &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;DEV Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Thu, 13 Aug 2026 04:03:42 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/-lmm</link>
      <guid>https://dev.to/varshithvhegde/-lmm</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd" class="crayons-story__hidden-navigation-link"&gt;I Built a Notebook for Sharing Notes That Doesn't Ask You to Sign Up First&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Discusses URL token trade-offs and privacy catches&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/varshithvhegde" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885064%2F4ab304f4-a3f3-409c-8217-9ce130e57c18.jpeg" alt="varshithvhegde profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/varshithvhegde" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Varshith V Hegde
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Varshith V Hegde
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4379248" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/varshithvhegde" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885064%2F4ab304f4-a3f3-409c-8217-9ce130e57c18.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Varshith V Hegde&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd" id="article-link-4379248"&gt;
          I Built a Notebook for Sharing Notes That Doesn't Ask You to Sign Up First
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/productivity"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;productivity&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;62&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              16&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            14 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I Built a Notebook for Sharing Notes That Doesn't Ask You to Sign Up First</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:53:06 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd</link>
      <guid>https://dev.to/varshithvhegde/i-built-a-notebook-for-sharing-notes-that-doesnt-ask-you-to-sign-up-first-2ldd</guid>
      <description>&lt;p&gt;Someone asked me to share meeting notes in Slack yesterday.&lt;/p&gt;

&lt;p&gt;I pasted the markdown. Slack ate the table. The code block lost its indentation. The task list rendered as literal &lt;code&gt;[ ]&lt;/code&gt; characters. I spent five minutes reformatting something that already looked perfect in my editor.&lt;/p&gt;

&lt;p&gt;So I sent a link instead.&lt;/p&gt;

&lt;p&gt;Not to Notion — they would have to sign up. Not to Google Docs — same problem, plus I did not want this living in someone's Drive forever. Not to a pastebin — those are single blobs of text with no structure, no pages, no way to come back and fix a typo tomorrow.&lt;/p&gt;

&lt;p&gt;I wanted: &lt;strong&gt;one link, multiple pages, markdown that actually renders, no account required, and a way for me to edit it later without the URL changing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have built sharing tools before. &lt;a href="https://github.com/Varshithvhegde/FreeShare" rel="noopener noreferrer"&gt;FreeShare&lt;/a&gt; for files. &lt;a href="https://github.com/Varshithvhegde/notepage" rel="noopener noreferrer"&gt;NotePage&lt;/a&gt; for single-page text. SharePad is what I wished existed when I needed something in between a pastebin and a wiki — but without the signup wall.&lt;/p&gt;

&lt;p&gt;Live here: &lt;strong&gt;&lt;a href="https://sharepad.in" rel="noopener noreferrer"&gt;https://sharepad.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/sharepad" rel="noopener noreferrer"&gt;https://github.com/Varshithvhegde/sharepad&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.sharepad.in/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsharepad.in%2Fopengraph-image.png%3Fopengraph-image.2aq3p9hbwln1v.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.sharepad.in/" rel="noopener noreferrer" class="c-link"&gt;
            SharePad — Share notes with one link, no signup
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Write a notebook of markdown pages and share it with a single link. Password lock, expiry dates, comments and PDF export. Free, and no account needed.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.sharepad.in%2Ffavicon.ico%3Ffavicon.3fpu2ql9ns1a0.ico" width="256" height="256"&gt;
          sharepad.in
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Most "share your notes" products follow the same playbook. Create an account. Verify your email. Create a workspace. Invite people. Configure permissions. By the time you are done, the meeting is over and nobody cares about the notes anymore.&lt;/p&gt;

&lt;p&gt;SharePad skips all of that.&lt;/p&gt;

&lt;p&gt;You write markdown. You get two links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;View link&lt;/strong&gt; — &lt;code&gt;/n/kitchen-reno&lt;/code&gt; — hand this out freely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit link&lt;/strong&gt; — &lt;code&gt;/e/{secret-token}&lt;/code&gt; — this is your ownership credential. Do not share it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No account. No password to create (unless you &lt;em&gt;want&lt;/em&gt; to lock the view link). No "upgrade to share with more people." The edit token &lt;em&gt;is&lt;/em&gt; your identity for that notebook.&lt;/p&gt;

&lt;p&gt;That one decision — token-based ownership instead of user accounts — simplified everything else. No auth flows. No session management. No "forgot password" for the app itself. Just a 32-byte secret generated at creation time, hashed with SHA-256, stored in Postgres, and shown to you exactly once.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstofjgbz5jrl5ng9s8wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstofjgbz5jrl5ng9s8wv.png" alt="Split editor with markdown on the left, live preview on the right, red margin rule visible" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Actually Do With It
&lt;/h2&gt;

&lt;p&gt;Before the architecture diagram, here is what this looks like when you are actually using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paste and share in ten seconds.&lt;/strong&gt; Go to &lt;code&gt;/quick&lt;/code&gt;, drop your markdown, get a link. Title comes from your first heading. Slug is generated automatically. Edit link lands on your clipboard. Done.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp8tnn6l5zbgtfd71cehz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp8tnn6l5zbgtfd71cehz.png" alt="Quick paste page — markdown in the box, share link ready to copy" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-page notebooks behind one URL.&lt;/strong&gt; This is the main thing that separates it from a pastebin. A notebook can have dozens of pages — meeting notes, appendix, todo list, reference docs — all navigable from a sidebar index. Readers see one link. You manage one notebook.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8zwd467im94e7pk550vh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8zwd467im94e7pk550vh.png" alt="Sidebar listing multiple pages in a single notebook" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick your own address.&lt;/strong&gt; At &lt;code&gt;/new&lt;/code&gt; you can claim &lt;code&gt;sharepad.in/n/kitchen-reno&lt;/code&gt; while you type. Taken names tell you immediately. Reserved slugs (&lt;code&gt;/new&lt;/code&gt;, &lt;code&gt;/quick&lt;/code&gt;, &lt;code&gt;/api&lt;/code&gt;, etc.) are blocked so you cannot accidentally break routing.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1edhoy9yfs4rugt4fuvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1edhoy9yfs4rugt4fuvf.png" alt="New notebook form with custom slug check and paper texture picker" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two levels of access.&lt;/strong&gt; Share the view link in Slack. Keep the edit link in your password manager. Or flip on &lt;strong&gt;open editing&lt;/strong&gt; and let anyone with the view link write content — useful for a shared grocery list or a retro board where you do not want people creating accounts.&lt;/p&gt;

&lt;p&gt;Important detail: open editing lets visitors change &lt;em&gt;content&lt;/em&gt;. Settings, expiry, password, and deletion always require the edit token. A visitor cannot lock you out of your own notebook.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60nonkjrkcynap6agxfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60nonkjrkcynap6agxfi.png" alt="Share panel with view link, edit link, and QR code" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock it down when you need to.&lt;/strong&gt; Password-protect the view link (bcrypt hashed). Flip read-only mode. Set burn-after-read so the link works exactly once. Choose visibility: public (indexable), unlisted (link only), or private (view link blocked entirely).&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7es19fdhodawpwtfpzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp7es19fdhodawpwtfpzg.png" alt="Settings panel with expiry, visibility, and access toggles" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paste screenshots straight into the editor.&lt;/strong&gt; This took longer to get right than I expected. You paste an image, it compresses in the browser, uploads to Cloudflare R2, and inserts the markdown link. More on that below.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpgshmyqu1cca9rempkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpgshmyqu1cca9rempkw.png" alt="Screenshot pasted into the editor and rendered in the live preview" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Print the whole thing as a real document.&lt;/strong&gt; &lt;code&gt;/n/slug/print&lt;/code&gt; strips the handwritten aesthetic and renders every page in Source Serif — title sheet, table of contents, page breaks. Save as PDF from the browser. Handwriting disappears. It looks like something you would hand to a client, not something you scribbled on a sticky note.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zxdr4q8r7lf6xzx7r3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zxdr4q8r7lf6xzx7r3c.png" alt="Print view with serif typography, title sheet and table of contents" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It cleans up after itself.&lt;/strong&gt; Notebooks expire in ten days by default. Push that to a year, or turn expiry off. When the timestamp passes, view links die immediately. Three days later a cron job deletes the row for real.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1m6kq0oi4r16o78azxwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1m6kq0oi4r16o78azxwr.png" alt="Home page hero — Write it down, Send one link, Free forever no signup" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Next.js 16&lt;/strong&gt; with the App Router and Turbopack. Server components for the initial notebook load, client components for the editor. The split view, auto-save, scroll sync, and toolbar all live in a single &lt;code&gt;NotebookEditor&lt;/code&gt; component that handles both view mode (&lt;code&gt;/n/slug&lt;/code&gt;) and edit mode (&lt;code&gt;/e/token&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase Postgres&lt;/strong&gt; for everything persistent. Notebooks, pages, version history, comments, image metadata, rate limit counters. Row-level security is enabled on every table, but there are no public write policies — the server talks to Postgres through the service role key. The anon key exists because Supabase expects it, but the app does not expose direct client writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS 4&lt;/strong&gt; for styling, but the visual identity is deliberately &lt;em&gt;not&lt;/em&gt; a generic SaaS dashboard. Kalam and Architects Daughter for handwriting. A red margin rule down each page. Taped sticky-note cards on the home page. I wanted it to feel like paper, not like yet another AI product with a purple gradient hero section.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;react-markdown&lt;/strong&gt; with remark-gfm for GitHub-flavoured markdown, rehype-highlight for code blocks, rehype-raw for inline HTML, and rehype-sanitize before anything hits the DOM. That last one is not optional — more on why in a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare R2&lt;/strong&gt; for images. I chose R2 over Supabase Storage because egress is free at any volume. Supabase's free tier shares egress between database and storage — one viral notebook with screenshots could take the whole app down. R2 charges nothing for egress, ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostHog&lt;/strong&gt; for analytics, optional and heavily restricted. Autocapture off. Session recording off on notebook routes. No note content, titles, slugs, or edit tokens in any event payload. An edit token is a write credential — putting one in analytics would hand notebook control to a third party.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; for two cron jobs: ping Supabase every three days so the free project does not pause, and sweep orphaned images from R2 weekly.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Ownership Works (Without Login)
&lt;/h2&gt;

&lt;p&gt;There is no users table. There is no signup form. Here is the full auth model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create notebook
  → server generates edit_token (32 random bytes)
  → stores SHA-256(edit_token) in notebooks.edit_token_hash
  → returns edit_token to the browser once
  → browser saves it in localStorage for "Saved notebooks" on the home page

View:  GET /n/{slug}           → anyone (unless password/expired/private)
Edit:  GET /e/{edit_token}     → validates token hash → full editor
Write: POST/PATCH/DELETE /api/* with X-Edit-Token header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Password-protected notebooks add one step:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/notebooks/unlock/{slug}  { password }
  → bcrypt compare
  → sets httpOnly cookie sp_unlock_{slug} for 24 hours
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The edit token never appears in URLs on the view route. The view route never accepts writes unless open editing is enabled. Settings always require the token.&lt;/p&gt;

&lt;p&gt;I have shipped products with full auth before. For this use case, accounts would have been pure friction. The edit link &lt;em&gt;is&lt;/em&gt; the account.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Editor
&lt;/h2&gt;

&lt;p&gt;The editor is a textarea and a preview pane. That is intentionally boring technology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Split view with scroll sync.&lt;/strong&gt; Write on the left, preview on the right. Scroll one, the other follows. On mobile it collapses to write-only because split view on a phone is unusable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toolbar actions participate in undo.&lt;/strong&gt; This was a subtle bug for a while. Typing supported Ctrl+Z fine, but clicking "bold" or "insert code block" did not — because I was replacing &lt;code&gt;textarea.value&lt;/code&gt; directly, which bypasses the browser's undo stack. Fix: &lt;code&gt;document.execCommand("insertText")&lt;/code&gt;. Yes, it is deprecated. It is also still the only way to get undoable programmatic edits in a plain textarea without building a full ProseMirror instance. I am not building ProseMirror for a side project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tab actually indents.&lt;/strong&gt; Default browser behaviour moves focus to the next button. SharePad intercepts Tab and inserts two spaces (or indents a whole selected block). Shift+Tab outdents. Esc+Tab is an escape hatch if you genuinely need to tab to the next UI element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version history.&lt;/strong&gt; Every page keeps its last ten drafts. Open history, pick an old version, restore it. Snapshots happen server-side on content change, not on every keystroke — the auto-save debounce would otherwise generate hundreds of useless versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page templates.&lt;/strong&gt; Meeting notes, project brief, weekly plan, journal entry. One click adds a pre-filled page with sensible headings.&lt;/p&gt;


&lt;h2&gt;
  
  
  Images (and the Bug That Only Bit Real Photographs)
&lt;/h2&gt;

&lt;p&gt;Paste a screenshot. It should just work.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Browser compresses the image — max 1600px edge, re-encoded as WebP at 82% quality. A phone screenshot typically shrinks by 10x before it ever hits the network.&lt;/li&gt;
&lt;li&gt;Server sniffs magic bytes (not the declared Content-Type, which the uploader chooses and proves nothing).&lt;/li&gt;
&lt;li&gt;Object stored in R2 under &lt;code&gt;{notebook_id}/{random-hex}.{ext}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Row inserted in the &lt;code&gt;images&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Markdown link inserted: &lt;code&gt;![alt](https://pub-....r2.dev/...)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Limits: 5 MB after compression, 50 images per notebook, 60 uploads per IP per ten minutes. Rate limiting runs in a single Postgres statement so two simultaneous uploads cannot both read the old count and slip through.&lt;/p&gt;

&lt;p&gt;The bug that wasted an afternoon: &lt;strong&gt;small pasted images worked. Large ones failed with "The image store did not accept that file."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Node's &lt;code&gt;fetch&lt;/code&gt; switches to chunked transfer encoding once the body passes a certain size. Cloudflare R2 answers a PUT without an explicit Content-Length with &lt;strong&gt;411 Missing ContentLength&lt;/strong&gt;. Small images fit in one chunk and happened to work. Real photographs did not.&lt;/p&gt;

&lt;p&gt;Fix was one header:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;headers&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;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Length&lt;/span&gt;&lt;span class="dl"&gt;"&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;byteLength&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cache-Control&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;public, max-age=31536000, immutable&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also learned that drawing an animated GIF to a canvas keeps the first frame and silently kills the animation, so GIFs pass through uncompressed. And that a placeholder of &lt;code&gt;⏳ uploading image 1…&lt;/code&gt; beats inserting &lt;code&gt;![]( )&lt;/code&gt; with an empty src, which makes the browser try to reload the entire page.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd1iqf2kl1mj6jevmbx6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd1iqf2kl1mj6jevmbx6.gif" alt="GIF — paste a screenshot and watch it upload and render in the preview" width="760" height="440"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  HTML in Markdown (and Why Sanitising Is Not Optional)
&lt;/h2&gt;

&lt;p&gt;SharePad renders raw HTML inside markdown — &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;mark&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;abbr&amp;gt;&lt;/code&gt;, table alignment attributes. Useful for collapsible sections and keyboard shortcuts.&lt;/p&gt;

&lt;p&gt;It also sanitises everything against GitHub's schema before rendering. Scripts, styles, iframes, objects, forms, event handlers, &lt;code&gt;javascript:&lt;/code&gt; URLs — all stripped.&lt;/p&gt;

&lt;p&gt;Why this matters: &lt;strong&gt;open editing&lt;/strong&gt; lets anyone with the view link write into the notebook. Without sanitisation, a visitor injects a script. The script runs on the &lt;em&gt;owner's&lt;/em&gt; browser when they open the edit link — where the edit token is sitting in the URL and every saved token is in &lt;code&gt;localStorage&lt;/code&gt;. One malicious notebook and you hand over every notebook that browser has ever created.&lt;/p&gt;

&lt;p&gt;The allowed set lives in &lt;code&gt;lib/markdown-schema.ts&lt;/code&gt;. I added a few presentational tags on top of GitHub's defaults. I did not relax anything about scripts.&lt;/p&gt;


&lt;h2&gt;
  
  
  Expiry (and the Grace Period Nobody Asked For But Everyone Needs)
&lt;/h2&gt;

&lt;p&gt;Choosing "10 days" stores an absolute timestamp, not a rolling countdown.&lt;/p&gt;

&lt;p&gt;Timeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Expiry moment&lt;/strong&gt; — view and print links return 404. Readers are locked out immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three days later&lt;/strong&gt; — &lt;code&gt;pg_cron&lt;/code&gt; deletes the notebook row. Pages, versions, comments cascade away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burn-after-read&lt;/strong&gt; — deleted one day after the single view.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The three-day gap between "readers locked out" and "row deleted" is deliberate. The owner can still open the edit link during that window and push the expiry date back. Immediate hard delete on expiry would mean one missed calendar reminder and your notes are gone with no recovery path.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'purge-expired-notebooks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'15 3 * * *'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purge_expired_notebooks&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Deleting a notebook through the app clears its R2 images immediately. Expired notebooks rely on a weekly sweep that lists the bucket, checks which notebook IDs still exist in Postgres, and deletes the orphans.&lt;/p&gt;


&lt;h2&gt;
  
  
  Some Honest Pain Points
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hydration mismatch on dates.&lt;/strong&gt; &lt;code&gt;toLocaleDateString()&lt;/code&gt; rendered &lt;code&gt;11/08/2026&lt;/code&gt; on the server and &lt;code&gt;8/11/2026&lt;/code&gt; on the client. Classic. Fixed with explicit &lt;code&gt;en-GB&lt;/code&gt; formatting in a shared utility so server and client agree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The red margin line floating in the wrong place.&lt;/strong&gt; On wide screens the margin rule was anchored to a full-width container while the text sat in a centred column. Huge gap between the red line and the words. Fix: move the margin rule inside the text column wrapper so it hugs the content regardless of viewport width.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS specificity vs Tailwind.&lt;/strong&gt; The &lt;code&gt;.sk&lt;/code&gt; paper-card class had &lt;code&gt;position: relative&lt;/code&gt; in global CSS. Tailwind's &lt;code&gt;absolute&lt;/code&gt; on dropdown menus lost the cascade battle. Menus clipped to a one-pixel sliver. Fix: wrap menus in a plain &lt;code&gt;div&lt;/code&gt; with inline positioning, move &lt;code&gt;.sk&lt;/code&gt; into &lt;code&gt;@layer components&lt;/code&gt; so utilities win.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5ujy9ttif66w4qvnj90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn5ujy9ttif66w4qvnj90.png" alt="Custom 404 page — torn paper notebook not found" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete notebook did not redirect.&lt;/strong&gt; The API returned 200, the notebook was gone, but the user sat on a dead &lt;code&gt;/e/{token}&lt;/code&gt; page staring at a frozen settings modal. &lt;code&gt;router.push("/")&lt;/code&gt; was a soft navigation that did not fully tear down the editor. Fix: &lt;code&gt;window.location.replace("/?deleted=1")&lt;/code&gt; — hard leave, no back button to a ghost URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limit too tight on images.&lt;/strong&gt; Twenty uploads per ten minutes sounded reasonable in a spreadsheet. In practice, illustrating a doc means a dozen screenshots in five minutes. Bumped to sixty. The per-notebook cap of fifty is what actually limits abuse; the IP limit just stops someone using the service as a free image host across hundreds of notebooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase free tier pauses after seven days idle.&lt;/strong&gt; A GitHub Action pings &lt;code&gt;/api/ping&lt;/code&gt; every three days. Boring infrastructure, but a paused database is a dead app.&lt;/p&gt;


&lt;h2&gt;
  
  
  SEO (Because Nobody Searches "SharePad")
&lt;/h2&gt;

&lt;p&gt;They search "share markdown without signup." "Online notepad no login." "Send notes without account."&lt;/p&gt;

&lt;p&gt;So there are landing pages for each phrase — &lt;code&gt;/share-markdown-without-signup&lt;/code&gt;, &lt;code&gt;/online-notepad-no-login&lt;/code&gt;, &lt;code&gt;/markdown-to-pdf&lt;/code&gt; — with actual content, not keyword stuffing. Plus &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt; for the crawlers that read those now. Sitemap, robots.txt, FAQ schema on the home page. The brand name does not matter for discovery. The landing pages do.&lt;/p&gt;


&lt;h2&gt;
  
  
  Running It Yourself
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Varshithvhegde/sharepad.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sharepad
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env.local
&lt;span class="c"&gt;# Fill in Supabase keys&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Apply the SQL migrations in &lt;code&gt;supabase/migrations/&lt;/code&gt; in order — six files, from initial schema through images and rate limits. Supabase CLI: &lt;code&gt;supabase db push&lt;/code&gt;. Or paste each file into the SQL editor manually.&lt;/p&gt;

&lt;p&gt;Minimum env:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...
NEXT_PUBLIC_SITE_URL=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Image upload stays off until all five &lt;code&gt;R2_*&lt;/code&gt; variables are set. The app runs fine without them — you just cannot paste screenshots.&lt;/p&gt;

&lt;p&gt;Deploy anywhere Next.js runs. Vercel is the path of least resistance. Set &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; to your real domain in production.&lt;/p&gt;

&lt;p&gt;Full setup docs: &lt;a href="https://github.com/Varshithvhegde/sharepad" rel="noopener noreferrer"&gt;github.com/Varshithvhegde/sharepad&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://sharepad.in" rel="noopener noreferrer"&gt;sharepad.in&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick path:&lt;/strong&gt; &lt;a href="https://sharepad.in/quick" rel="noopener noreferrer"&gt;sharepad.in/quick&lt;/a&gt; — paste markdown, get a link&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full create:&lt;/strong&gt; &lt;a href="https://sharepad.in/new" rel="noopener noreferrer"&gt;sharepad.in/new&lt;/a&gt; — custom slug, paper texture, font, expiry, password&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recover a lost edit link:&lt;/strong&gt; &lt;a href="https://sharepad.in/recover" rel="noopener noreferrer"&gt;sharepad.in/recover&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Varshithvhegde" rel="noopener noreferrer"&gt;
        Varshithvhegde
      &lt;/a&gt; / &lt;a href="https://github.com/Varshithvhegde/sharepad" rel="noopener noreferrer"&gt;
        sharepad
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Share multi-page markdown notebooks instantly — no signup, no login
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SharePad&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Write a notebook of markdown pages and share it with a single link. &lt;strong&gt;No signup, no login.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Varshithvhegde/sharepad/./public/screenshot.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVarshithvhegde%2Fsharepad%2FHEAD%2F.%2Fpublic%2Fscreenshot.png" alt="SharePad"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;Multi-page notebooks&lt;/strong&gt; behind one address, with a tabbed page index&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Paste and share&lt;/strong&gt; — drop text in, get a link back, title and address chosen for you&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Your own address&lt;/strong&gt; — &lt;code&gt;/n/kitchen-reno&lt;/code&gt;, checked for availability as you type&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Expiry&lt;/strong&gt; — 10 days by default, adjustable up to a year or off entirely&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Two links&lt;/strong&gt; — a view link to hand out, a secret edit link you keep&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Open editing&lt;/strong&gt; — optionally let anyone with the link write in it too&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Password lock&lt;/strong&gt;, read-only mode, and burn-after-reading&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Version history&lt;/strong&gt; — the last ten drafts of every page, restorable&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Comments&lt;/strong&gt; from readers, no account required&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;PDF export&lt;/strong&gt; — the whole notebook as a clean document, no handwriting&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Markdown export&lt;/strong&gt; and &lt;code&gt;.md&lt;/code&gt; import&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Paper and typeface&lt;/strong&gt; — ruled, grid, dotted…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Varshithvhegde/sharepad" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;If something breaks, &lt;a href="https://github.com/Varshithvhegde/sharepad/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;. I read them.&lt;/p&gt;




&lt;p&gt;The thing I keep coming back to is how much friction we accept as normal. Sign up. Verify email. Create a workspace. Configure sharing. For &lt;em&gt;notes&lt;/em&gt;. Most of the time you just want to hand someone a link and move on with your day.&lt;/p&gt;

&lt;p&gt;SharePad is my attempt to make that link worth opening — proper markdown, multiple pages, a print view that does not embarrass you, screenshots that paste inline, and an expiry date so old notes do not haunt you forever.&lt;/p&gt;

&lt;p&gt;If you build something on top of it, or fork it and take it somewhere I did not think of, I want to see it. Drop a comment or ping me on &lt;a href="https://dev.to/varshithvhegde"&gt;dev.to&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you have ever lost meeting notes because Slack mangled your table formatting — same. That is literally why this exists.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Chat App That Rewrites Its Own UI in Real Time</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Tue, 28 Jul 2026 18:35:05 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/i-built-a-chat-app-that-rewrites-its-own-ui-in-real-time-21m5</link>
      <guid>https://dev.to/varshithvhegde/i-built-a-chat-app-that-rewrites-its-own-ui-in-real-time-21m5</guid>
      <description>&lt;p&gt;So I had this idea that kept nagging at me.&lt;/p&gt;

&lt;p&gt;Every AI chat app works the same way. You type something, the model returns text or markdown, the UI renders it as a nice formatted paragraph. That is fine if you want an answer. It is genuinely boring if you want to actually &lt;em&gt;build&lt;/em&gt; something.&lt;/p&gt;

&lt;p&gt;What if the AI could respond with a working game board you could click? What if saying "make it Barbie themed" actually transformed the whole interface while you watched? What if "add a starfield in the background" dropped an animated canvas behind your chat in real time?&lt;/p&gt;

&lt;p&gt;I spent a few weeks building exactly that. I call it &lt;strong&gt;FlowChat&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is the live version: &lt;strong&gt;&lt;a href="https://flowchat-public.varshithvh.workers.dev" rel="noopener noreferrer"&gt;https://flowchat-public.varshithvh.workers.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And yes, someone immediately asked it to play Tic Tac Toe and then asked it to switch to an Oppenheimer theme mid-game. I could not be prouder.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Normal AI chat: model returns markdown, client renders it as text. Simple, predictable, boring.&lt;/p&gt;

&lt;p&gt;FlowChat: model returns raw HTML with CSS and JavaScript, client injects it directly into the DOM using a streaming protocol built on the browser's native template system.&lt;/p&gt;

&lt;p&gt;That one change makes the entire experience different. You are not reading about a game. You are playing one. You are not reading about a Barbie color palette. You are sitting inside one.&lt;/p&gt;

&lt;p&gt;The AI does not just answer questions. It &lt;em&gt;rebuilds the UI from its responses&lt;/em&gt;.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffgjlxkl0lnxi912xwiga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffgjlxkl0lnxi912xwiga.png" alt="Screenshot of Tic Tac Toe game running live inside a chat bubble" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Actually Do With It
&lt;/h2&gt;

&lt;p&gt;I want to give you a feel for what this means in practice before getting into the technical bits, because the demos are more interesting than any architecture diagram.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Games&lt;/strong&gt;: Ask it to build Tic Tac Toe. You get a playable board, click-to-move, an AI opponent, win detection. Ask for Connect 4. Ask for Snake. The game renders in the chat as an agent bubble with a form inside it. Each move submits to the LLM which processes it and updates only the cells that changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Themes&lt;/strong&gt;: Say "change to a Barbie theme". The model injects CSS overrides and the whole interface turns pink. Messages, borders, buttons, the prompt box. Say "Oppenheimer themed". You get dark sepia tones and heavy typography. The sidebar and topbar stay locked so the shell never breaks, but everything inside the chat transforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backgrounds&lt;/strong&gt;: Say "add a starfield". An animated canvas renders behind your messages. Say "DVD bounce animation". The logo bounces around the chat viewport. Say "use a space image". An image fills the background. All of this lives in a contained layer so it never covers the actual UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full interface takeover&lt;/strong&gt;: At one point I asked it to make the page look like Wikipedia. It replaced the prompt box with links. Clicking any link submitted a form back to the LLM which generated a new article replacing the chat content. I was reading about the Roman Empire in a chat app I built on a Sunday afternoon.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffexg1cdgc19mjan1fi82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffexg1cdgc19mjan1fi82.png" alt="Barbie themed interface with pink gradients" width="800" height="457"&gt;&lt;/a&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pagnbmhuwg7qk19ddl2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pagnbmhuwg7qk19ddl2.png" alt="Space background visible behind chat messages" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;Everything runs on Cloudflare's edge infrastructure. No traditional server. No Node.js process to keep alive. No managed database to worry about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; runs the TypeScript on every request. Cold starts are under 50ms globally. The whole worker is one file that handles routing, auth, rate limiting, WebSocket upgrades, and LLM streaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Durable Objects&lt;/strong&gt; is the part that makes this work. Each chat room is a single Durable Object: a stateful actor with its own SQLite database, its own in-memory queue, and its own WebSocket connections. When you and a friend open the same chat URL, you both connect to the same DO. Sync is not something you have to build. It is just how the architecture works.&lt;/p&gt;

&lt;p&gt;Each DO stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The full LLM message history in SQLite&lt;/li&gt;
&lt;li&gt;Client session records&lt;/li&gt;
&lt;li&gt;A queue of pending prompts (max 5)&lt;/li&gt;
&lt;li&gt;Rate limit state per browser/IP&lt;/li&gt;
&lt;li&gt;A fork index for read-only snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hibernatable WebSockets&lt;/strong&gt; keep connections alive without keeping the DO alive. Cloudflare auto-handles ping/pong. The DO wakes up when a message arrives and goes back to sleep between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;better-auth&lt;/strong&gt; handles optional authentication. If you do not configure it, the app is open to everyone. If you do, you get Google, GitHub, and email/password with role-based access (admin, dev, chat, view, blocked).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inception Labs Mercury-2&lt;/strong&gt; is the model powering responses. It is a diffusion-based language model rather than autoregressive, which means it generates differently to GPT or Claude. In practice it feels fast and it seems to genuinely understand the HTML output format I need from 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F80alc7ztjomcpceou7zf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F80alc7ztjomcpceou7zf.png" alt="Wrangler terminal showing bindings after deploy" width="800" height="969"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Protocol
&lt;/h2&gt;

&lt;p&gt;This was the most enjoyable part to design and the part I am most proud of.&lt;/p&gt;

&lt;p&gt;The AI cannot just dump raw HTML into a response stream. A single response might need to update three different parts of the page independently. A Tic Tac Toe move should update one cell, not redraw the entire board. A background animation should not affect the sidebar. A private message to one player should not appear in the other player's chat.&lt;/p&gt;

&lt;p&gt;So I built a delimiter-based streaming protocol. The model wraps every DOM update in a structured envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;PpqUtcLGQdYN4oqc:BODY_START
&lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"/chat/append-message"&lt;/span&gt;&lt;span class="nt"&gt;&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;"message message-user"&lt;/span&gt; &lt;span class="na"&gt;data-client-id=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Lets play Tic Tac Toe&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;"message message-agent message-full-width"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"msg-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- entire game board HTML --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"/chat/append-message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
PpqUtcLGQdYN4oqc:BODY_END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;for&lt;/code&gt; attribute on the template targets a named marker in the DOM. The client runtime walks the document tree looking for processing instructions with matching names and replaces them with the template content. Surgically. Without touching anything else on the page.&lt;/p&gt;

&lt;p&gt;A single AI response can contain multiple messages separated by a split delimiter:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PpqUtcLGQdYN4oqc:SPLIT_MESSAGE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So the model can send a public chat confirmation to all users AND simultaneously route a private message to only one player by including SERVER_PROPS routing instructions that the server strips before forwarding over WebSockets.&lt;/p&gt;

&lt;p&gt;The whole thing is built on two browser polyfills that implement the &lt;a href="https://developer.chrome.com/blog/declarative-partial-updates" rel="noopener noreferrer"&gt;Dynamic Partial Update&lt;/a&gt; spec that is landing in Chrome.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsni33heqf1ens87xtgp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsni33heqf1ens87xtgp2.png" alt="Debug view showing raw LLM message history with protocol delimiters visible" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Writing the System Prompt
&lt;/h2&gt;

&lt;p&gt;Getting the AI to consistently produce valid HTML inside this protocol format took a lot of iteration. The final system prompt is about 300 lines and honestly reads more like an API contract than a prompt.&lt;/p&gt;

&lt;p&gt;It covers the exact hex values of every CSS variable in the design system so the model writes &lt;code&gt;var(--accent)&lt;/code&gt; correctly instead of guessing colors. Rules for border-radius, shadow values, animation timing. The async CDN loading pattern for Chart.js and d3, because the model kept calling &lt;code&gt;new Chart()&lt;/code&gt; before the library loaded.&lt;/p&gt;

&lt;p&gt;The biggest bug I chased was this one: the model kept placing the append-message marker &lt;em&gt;inside&lt;/em&gt; the app container div instead of after it. Every subsequent chat message would inject into the game board. I fixed it with a wrong-vs-correct example in the prompt:&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="c"&gt;&amp;lt;!-- WRONG: marker inside app div, next message injects here forever --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ttt-app-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...board...
  &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"/chat/append-message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- CORRECT: marker after ALL divs close --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ttt-app-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...board...
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;&lt;span class="na"&gt;marker&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"/chat/append-message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Wrong examples with explicit comments are more useful than correct-only documentation. The model needs to know what the failure mode looks like, not just the happy path.&lt;/p&gt;

&lt;p&gt;I also learned that diffusion models like Mercury-2 need slightly different prompting than autoregressive models. The responses feel less like a typewriter and more like content materializing. It pairs naturally with this use case.&lt;/p&gt;


&lt;h2&gt;
  
  
  Multi-User by Default
&lt;/h2&gt;

&lt;p&gt;Every chat URL is shared. Open the same link in two browser tabs and both receive every AI response over WebSockets in real time. Each client gets a unique ID. User bubbles are color-coded per client.&lt;/p&gt;

&lt;p&gt;The LLM knows each client's ID:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1]: I want to guess a secret word
[2]: I want to give the hint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The model can respond with one message visible to both players and a second message containing the secret visible only to client 1. Routed server-side, stripped from WebSocket payloads before they reach the wrong browser.&lt;/p&gt;

&lt;p&gt;I did not add any special multi-user logic. The Durable Object architecture just makes it work naturally. Every client connects to the same DO instance. The DO has the WebSocket connections. When the LLM responds, the DO broadcasts to all of them.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr62njdfnp7dn9hk39hug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr62njdfnp7dn9hk39hug.png" alt="Two browser windows on the same chat URL receiving the same message" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The UI
&lt;/h2&gt;

&lt;p&gt;Pure CSS. No framework, no Tailwind, no component library. Inter font loaded non-blocking, a deep navy palette (&lt;code&gt;#06091a&lt;/code&gt; to &lt;code&gt;#101630&lt;/code&gt;), periwinkle indigo accent (&lt;code&gt;#5b6ef5&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The app shell is a sidebar plus a main area with topbar. The sidebar and topbar are always physically opaque. The chat viewport is the only zone where themes and backgrounds can render. This prevents the AI from accidentally covering the navigation with a space photo, which it absolutely would do otherwise. I know because it did, many times, before I fixed the containment.&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;.chat-viewport&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isolate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#fc-bg-layer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;inset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pointer-events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;.chat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&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 background layer sits at z-index 0. Chat messages sit at z-index 2. The sidebar and topbar are separate elements outside the viewport entirely. Structural containment beats trying to enforce it with &lt;code&gt;!important&lt;/code&gt; and MutationObservers, which I tried first and which caused an infinite loop that froze the whole page. Lesson learned.&lt;/p&gt;

&lt;p&gt;Typing indicator, optimistic user bubbles, spring entrance animations on messages. The send button has a glow. It is small things but they add up.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw1bw4lmwnvpsrvaze8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw1bw4lmwnvpsrvaze8i.png" alt="Default dark UI with welcome screen and suggestion cards" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Some Honest Pain Points
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The model marker placement bug&lt;/strong&gt; took two days to properly fix because the issue was invisible until the second message arrived. The first message always looked correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The background containment wars&lt;/strong&gt; took about a week of back-and-forth. I tried CSS &lt;code&gt;!important&lt;/code&gt;, then a MutationObserver enforcer, then a JS-level background lock. All of them broke something else. The right answer was structural: move the background layer inside the chat viewport so it is physically impossible for it to escape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDN script loading&lt;/strong&gt; trips up every AI-generated app. The model writes code that calls &lt;code&gt;Chart.js&lt;/code&gt; APIs before the library loads. The fix is teaching it to poll:&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;function&lt;/span&gt; &lt;span class="nf"&gt;init&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Chart&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&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="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&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="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// safe to use Chart here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That pattern is now baked into the system prompt and it works reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The form action URL bug&lt;/strong&gt; was embarrassing. The form action in &lt;code&gt;app.html&lt;/code&gt; was &lt;code&gt;c/CHAT_ID/prompt&lt;/code&gt; (relative) instead of &lt;code&gt;/c/CHAT_ID/prompt&lt;/code&gt; (absolute). On a fresh load the path resolved correctly. After a redirect it did not. Every prompt submitted to &lt;code&gt;/c/c/CHAT_ID/prompt&lt;/code&gt; and got a 404. I caught it from the server logs and added a global form submit interceptor that normalizes any relative action URL before submission, as a safety net for AI-generated forms too.&lt;/p&gt;


&lt;h2&gt;
  
  
  Deploying
&lt;/h2&gt;

&lt;p&gt;The whole thing runs on Cloudflare's free tier. One command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler deploy &lt;span class="nt"&gt;--env&lt;/span&gt; public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;No Docker. No server to provision. No database UI to configure. Cloudflare handles scaling, WebSocket hibernation, global distribution, and the SQLite storage inside each Durable Object automatically.&lt;/p&gt;

&lt;p&gt;Secrets like the API key are stored via Wrangler:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler secret put INCEPTION_API_KEY &lt;span class="nt"&gt;--env&lt;/span&gt; public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;They never touch the codebase or version control.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Live: &lt;strong&gt;&lt;a href="https://flowchat-public.varshithvh.workers.dev" rel="noopener noreferrer"&gt;https://flowchat-public.varshithvh.workers.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/flowchat" rel="noopener noreferrer"&gt;https://github.com/Varshithvhegde/flowchat&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open a new chat. Type anything. Ask it to build a game, change the theme, add a background, or make the page look like something completely different. It will.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Varshithvhegde" rel="noopener noreferrer"&gt;
        Varshithvhegde
      &lt;/a&gt; / &lt;a href="https://github.com/Varshithvhegde/flowchat" rel="noopener noreferrer"&gt;
        flowchat
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Multi-user AI chat that generates live HTML UI — games, dashboards, apps — powered by Cloudflare Workers + Durable Objects
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;FlowChat&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A multi-user AI chat where the model responds with live HTML instead of markdown. Every reply can be a game, dashboard, animated background, interactive form, or a full UI redesign — running directly in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://flowchat-public.varshithvh.workers.dev" rel="nofollow noopener noreferrer"&gt;https://flowchat-public.varshithvh.workers.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/80502833/627999367-0e25c7ce-e127-4246-a011-22936fc3d722.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODUyNjQwMDUsIm5iZiI6MTc4NTI2MzcwNSwicGF0aCI6Ii84MDUwMjgzMy82Mjc5OTkzNjctMGUyNWM3Y2UtZTEyNy00MjQ2LWEwMTEtMjI5MzZmYzNkNzIyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzI4VDE4MzUwNVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU2Yjg2NDk3ODA0YWEyMjM5ZjFiZTg2Nzk5ZTBhZmIxYWFlODYwZjRmNTAxZTNmY2UxNzhjOTk0MjQ3NGZkNzEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.tsggFVGnYaIaR4Fpfm08mXTcJYFZkkmUpyBAT4uhq8c"&gt;&lt;img width="800" height="457" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F80502833%2F627999367-0e25c7ce-e127-4246-a011-22936fc3d722.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODUyNjQwMDUsIm5iZiI6MTc4NTI2MzcwNSwicGF0aCI6Ii84MDUwMjgzMy82Mjc5OTkzNjctMGUyNWM3Y2UtZTEyNy00MjQ2LWEwMTEtMjI5MzZmYzNkNzIyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA3MjglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNzI4VDE4MzUwNVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU2Yjg2NDk3ODA0YWEyMjM5ZjFiZTg2Nzk5ZTBhZmIxYWFlODYwZjRmNTAxZTNmY2UxNzhjOTk0MjQ3NGZkNzEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRnBuZyJ9.tsggFVGnYaIaR4Fpfm08mXTcJYFZkkmUpyBAT4uhq8c" class="js-gh-image-fallback"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;The AI responds with raw HTML, CSS, and JS — not markdown&lt;/li&gt;
&lt;li&gt;Updates are injected into the page using a streaming partial-update protocol&lt;/li&gt;
&lt;li&gt;Multiple users on the same URL see updates in real time over WebSockets&lt;/li&gt;
&lt;li&gt;The AI can target specific parts of the page independently (update one game cell, not the whole board)&lt;/li&gt;
&lt;li&gt;Style themes, background animations, and full interface redesigns all work via CSS marker injection&lt;/li&gt;
&lt;li&gt;Each chat room is a Cloudflare Durable Object with its own SQLite storage and WebSocket connections&lt;/li&gt;
&lt;/ul&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Stack&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Layer&lt;/th&gt;

&lt;th&gt;Technology&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;Runtime&lt;/td&gt;

&lt;td&gt;Cloudflare Workers&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;State + WebSockets&lt;/td&gt;

&lt;td&gt;Cloudflare Durable Objects&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;Storage&lt;/td&gt;

&lt;td&gt;SQLite (via Durable Object storage)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;Auth (optional)&lt;/td&gt;

&lt;td&gt;better-auth&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;AI Model&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;…&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Varshithvhegde/flowchat" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;p&gt;The thing I keep coming back to is how much of this was just moving one assumption. Instead of "the AI returns text and the UI renders it", it became "the AI returns HTML and the browser runs it". That one change opened up everything else.&lt;/p&gt;

&lt;p&gt;If you have questions about the protocol, the Durable Objects architecture, or the system prompt engineering, ask in the comments. I spent a lot of time on all three and I am happy to go deeper on any of it.&lt;/p&gt;

&lt;p&gt;And if you build something interesting with it, or fork it and take it somewhere I did not think of, I genuinely want to see it.&lt;/p&gt;

&lt;p&gt;You can also find me on LinkedIn and Dev.to. I write about things I am actually building, not things I think I should be building. There is a difference.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvsbgxpniz0p63x6wq7zy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvsbgxpniz0p63x6wq7zy.gif" alt="Your favorite screenshot from testing, whatever made you laugh or surprised you most" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Graveyard for My Dead Side Projects - With AI Eulogies &amp; a 3D Cemetery</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Sun, 12 Jul 2026 12:23:59 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/i-built-a-graveyard-for-my-dead-side-projects-with-ai-eulogies-a-3d-cemetery-5g0e</link>
      <guid>https://dev.to/varshithvhegde/i-built-a-graveyard-for-my-dead-side-projects-with-ai-eulogies-a-3d-cemetery-5g0e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every developer has a graveyard of side projects — started with fire, abandoned quietly on a Tuesday. They deserved better than an empty GitHub repo gathering digital dust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DevGraveyard&lt;/strong&gt; is a gothic memorial platform where developers give their abandoned passion projects a proper burial. Connect your GitHub, pick a dead repo, carve its epitaph — and watch Gemini AI write a dramatic breakup letter from you to the project.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbwas5bvcius26lawwg3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqbwas5bvcius26lawwg3.jpg" alt="01_landing_howItWorks" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚰️ &lt;strong&gt;Bury a project&lt;/strong&gt; — 3-step burial wizard: pick a repo → choose cause of death (&lt;em&gt;"Never Made it Past Localhost"&lt;/em&gt;, &lt;em&gt;"Ran Out of Weekend"&lt;/em&gt;, &lt;em&gt;"It Was Complicated"&lt;/em&gt;...) → write an epitaph&lt;/li&gt;
&lt;li&gt;🪦 &lt;strong&gt;Real tombstone data&lt;/strong&gt; — pulls your actual commit history: peak obsession streak, most commits in a single day, last commit message (&lt;em&gt;your final words&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;AI Eulogy&lt;/strong&gt; — Google Gemini writes a dramatic breakup letter from you to the project, referencing your real commit data&lt;/li&gt;
&lt;li&gt;🕯️ &lt;strong&gt;Community mourning&lt;/strong&gt; — light candles, leave RIP messages, vote to resurrect projects&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;3D Graveyard&lt;/strong&gt; — a full Three.js interactive cemetery: bare trees, fireflies, flickering candles, soul wisps, resurrection pulse rings. Click any tombstone to interact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My own &lt;code&gt;ARweave&lt;/code&gt; repo had 56 commits, a 2-day peak streak, 30 commits on its best day. Cause of death: &lt;em&gt;"Never Made it Past Localhost."&lt;/em&gt; Last words: &lt;em&gt;"feat: overlay plane in 3D builder — drag/scale image on marker, position saved to DB and restored in AR viewer."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It worked until it worked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live → &lt;a href="https://devgraveyard.varshithvhegde.in/" rel="noopener noreferrer"&gt;devgraveyard.varshithvhegde.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Varshithvhegde" rel="noopener noreferrer"&gt;
        Varshithvhegde
      &lt;/a&gt; / &lt;a href="https://github.com/Varshithvhegde/devgraveyard" rel="noopener noreferrer"&gt;
        devgraveyard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Give your abandoned passion projects a proper burial. A gothic graveyard for dead side projects.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;⚰️ DevGraveyard&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;A memorial for your abandoned side projects. They deserved better than an empty GitHub repo gathering digital dust.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Live → &lt;a href="https://devgraveyard.varshithvhegde.in" rel="nofollow noopener noreferrer"&gt;devgraveyard.varshithvhegde.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is this?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Every developer has a graveyard of passion projects — started with fire, abandoned quietly on a Tuesday. DevGraveyard gives them a proper burial.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bury a project&lt;/strong&gt; — connect GitHub, pick a dead repo, choose a cause of death (&lt;em&gt;"Never Made it Past Localhost"&lt;/em&gt;, &lt;em&gt;"Ran Out of Weekend"&lt;/em&gt;, &lt;em&gt;"It Was Complicated"&lt;/em&gt;...), write an epitaph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real tombstone data&lt;/strong&gt; — pulls your actual commit history: peak streak, most commits in a day, last commit message as "final words"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Eulogy&lt;/strong&gt; — Google Gemini writes a dramatic breakup letter from you to the project, referencing your real commit data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community mourning&lt;/strong&gt; — light candles, leave RIP messages, vote to resurrect projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3D Graveyard&lt;/strong&gt; — a full Three.js interactive cemetery at &lt;code&gt;/graveyard-3d&lt;/code&gt;. Click tombstones…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Varshithvhegde/devgraveyard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&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;Frontend&lt;/td&gt;
&lt;td&gt;Next.js 14 (App Router), TypeScript, Tailwind CSS, shadcn/ui&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth + Database&lt;/td&gt;
&lt;td&gt;Supabase (GitHub OAuth, Postgres, Row Level Security)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;Google Gemini &lt;code&gt;gemini-2.5-flash&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D&lt;/td&gt;
&lt;td&gt;Three.js + React Three Fiber + @react-three/drei&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animations&lt;/td&gt;
&lt;td&gt;Motion (Framer Motion successor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Step 1 — Burying a Project
&lt;/h3&gt;

&lt;p&gt;When you click &lt;strong&gt;"Bury a Project"&lt;/strong&gt;, a 3-step wizard walks you through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose Victim&lt;/strong&gt; — your GitHub repos load via the API. Already-buried repos show an &lt;em&gt;"already buried"&lt;/em&gt; badge and are disabled so you can't bury the same project twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the Epitaph&lt;/strong&gt; — pick a cause of death from a curated list or write your own. Add an optional epitaph (100 chars max). In the background we fetch your full commit history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm Burial&lt;/strong&gt; — a live tombstone preview renders with your real data before you commit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn88rmfmwi29e7mgh7z9e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn88rmfmwi29e7mgh7z9e.jpg" alt="05_bury_cause_of_death" width="800" height="502"&gt;&lt;/a&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakywjjdrc39rjqz2bx4u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fakywjjdrc39rjqz2bx4u.jpg" alt="05_bury_cause_of_death" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2 — Commit History as Emotional Data
&lt;/h3&gt;

&lt;p&gt;This is the technical heart of the project. When you bury a repo, we paginate through the entire commit history via the GitHub API and compute what I call &lt;strong&gt;"obsession data"&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From src/lib/github/analyze.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computePeakObsession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GitHubCommit&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// commits per day → longest consecutive streak&lt;/span&gt;
  &lt;span class="c1"&gt;// latest commit between midnight–5am → "latest night session"&lt;/span&gt;
  &lt;span class="c1"&gt;// max commits in a single day → "best day"&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These numbers feed directly into the tombstone — and into the Gemini prompt. A project that died after 30 commits on its best day tells a different story than one with 3 total commits.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3 — The AI Eulogy
&lt;/h3&gt;

&lt;p&gt;The eulogy prompt is carefully engineered to produce something specific, not generic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write exactly 3 paragraphs. Format as a letter FROM the developer
TO the project. Tone: dramatic, darkly funny, genuinely melancholic.
Opening: "Dear {repo_name},"
Reference at least 2 of these real data points:
  - Peak obsession: 30 commits in a single day
  - Latest night session: 2:34 AM  
  - Cause of death: "Never Made it Past Localhost"
  - Last commit message: "feat: overlay plane in 3D builder..."
Close with: "Yours, but not anymore, — A Tired Developer"
Max 250 words. No markdown.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results are genuinely surprising. Gemini knows you committed at 2 AM. It writes about that specific obsession. Here's what it produced for my &lt;code&gt;ARweave&lt;/code&gt; project:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsry7dh97uhn03ec3hs2a.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsry7dh97uhn03ec3hs2a.jpg" alt="12_eulogy_complete" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I remember the fervor, the peak obsession when I clocked 30 commits in a single day, mapping out every PLpgSQL schema and every front-end interaction. We built features that felt so robust within the confines of our little local development environment. You were a vibrant, if demanding, companion, demanding all my CPU cycles and mental bandwidth..."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The eulogy reveals with a typewriter animation when first generated, then persists in Supabase forever.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4 — The 3D Graveyard
&lt;/h3&gt;

&lt;p&gt;The 3D view at &lt;code&gt;/graveyard-3d&lt;/code&gt; is a full Three.js scene built with React Three Fiber.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tombstone shape&lt;/strong&gt; is a single &lt;code&gt;ExtrudeGeometry&lt;/code&gt; from a &lt;code&gt;THREE.Shape&lt;/code&gt; — a rectangle with &lt;code&gt;absarc&lt;/code&gt; for the semicircular arch. Much cleaner than a box + half-cylinder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeTombShape&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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.95&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;shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;absarc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// perfect semicircle&lt;/span&gt;
  &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;shape&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;strong&gt;Animations in the scene:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tombstones &lt;strong&gt;rise from underground&lt;/strong&gt; on load, staggered by index (ease-out cubic)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FlickerCandle&lt;/code&gt; — cone flame with per-frame scale noise + matching &lt;code&gt;PointLight&lt;/code&gt; intensity flicker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SoulWisps&lt;/code&gt; — glowing orbs float upward from tombstones with candles lit&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ResurrectPulse&lt;/code&gt; — an expanding &lt;code&gt;ringGeometry&lt;/code&gt; on the ground below voted tombstones&lt;/li&gt;
&lt;li&gt;55 firefly particles with sine-wave drift&lt;/li&gt;
&lt;li&gt;Fog, stars, bare winter trees, directional moonlight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You can light candles and vote to resurrect directly from the 3D panel&lt;/strong&gt; — it calls the real API and the stone reacts in real time.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgbm6gqvlobt3gz713rur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgbm6gqvlobt3gz713rur.png" alt="17_3d_info_panel" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The Public Graveyard Wall
&lt;/h3&gt;

&lt;p&gt;Every buried project joins the public memorial wall at &lt;code&gt;/graveyard&lt;/code&gt;, sortable by newest, most mourned, or most resurrection votes.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Forqp5dwj0mjwe05tc8h9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Forqp5dwj0mjwe05tc8h9.png" alt="18_graveyard_list" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Design Philosophy
&lt;/h3&gt;

&lt;p&gt;The entire aesthetic is built around one idea: &lt;strong&gt;this should feel like a real memorial, not a joke&lt;/strong&gt;. Developers genuinely grieve abandoned projects. The tombstones use engraved text, chiseled dividers, moss at the base. The AI eulogy takes commit data seriously. The community features are real interactions — your candle is stored in a database, your RIP message has an author and a timestamp.&lt;/p&gt;

&lt;p&gt;The passion isn't just the theme. It's the subject matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🏆 Best Use of Google AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DevGraveyard uses &lt;strong&gt;Google Gemini&lt;/strong&gt; (&lt;code&gt;gemini-2.5-flash&lt;/code&gt;) as the emotional core of the product. The eulogy generation prompt is engineered to reference specific real data points from the user's commit history — producing output that feels genuinely personal rather than generic AI text.&lt;/p&gt;

&lt;p&gt;The key insight: the AI isn't just generating &lt;em&gt;content&lt;/em&gt;, it's transforming raw GitHub telemetry (commit counts, timestamps, last message) into something that makes you &lt;em&gt;feel&lt;/em&gt; the loss of a project you actually cared about.&lt;/p&gt;

&lt;p&gt;The eulogy is generated once per tombstone (owner only), stored permanently in Supabase, and revealed with a typewriter animation. It costs one API call and lasts forever — the project's eulogy becomes part of its memorial.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built in a weekend. My &lt;code&gt;ARweave&lt;/code&gt; repo will never see production. But now it has a tombstone. That's something.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⚰️ &lt;strong&gt;&lt;a href="https://devgraveyard.varshithvhegde.in/" rel="noopener noreferrer"&gt;devgraveyard.varshithvhegde.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>1,200 Applications. 4 Offers. Here's What Actually Got Me the Product-Based Role</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Fri, 26 Jun 2026 06:36:53 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/1200-applications-4-offers-heres-what-actually-got-me-the-product-based-role-10mp</link>
      <guid>https://dev.to/varshithvhegde/1200-applications-4-offers-heres-what-actually-got-me-the-product-based-role-10mp</guid>
      <description>&lt;p&gt;I am going to start with a number most people will not say out loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1,200 applications.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is how many jobs I applied to over 3 to 4 months trying to switch from a service-based company to a product-based one. I had spreadsheets, saved searches, and browser tabs I kept telling myself I would close tomorrow. Some nights I was applying at 11pm just to hit my self-imposed daily quota.&lt;/p&gt;

&lt;p&gt;Out of 1,200, I got around 10 interview calls. Out of 10, I got 4 offers.&lt;/p&gt;

&lt;p&gt;The applications got me in the room. What happened inside the room is what this post is actually about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One Thing That Followed Me Into Every Interview
&lt;/h2&gt;

&lt;p&gt;At my previous company I worked on a lot of things, but one project came up in literally every single interview.&lt;/p&gt;

&lt;p&gt;We had a Python module that parsed ASAM MDF files. Binary log files from vehicles and sensors, often gigabytes in size. The parser was painfully slow. Around 8 minutes to load a single file. The kind of slow where you start it, go get lunch, and hope it is done when you come back.&lt;/p&gt;

&lt;p&gt;I rewrote it in Rust. Load time dropped from 8 minutes to 12 seconds. 40x improvement on GB-scale files.&lt;/p&gt;

&lt;p&gt;Every interviewer stopped me the moment I mentioned it. The questions were real engineering questions, not generic resume stuff.&lt;/p&gt;

&lt;p&gt;"Why Rust over Go or C++?"&lt;br&gt;
"How did you profile the bottleneck first?"&lt;br&gt;
"What was your testing strategy when rewriting something this critical?"&lt;br&gt;
"What would you do differently now?"&lt;/p&gt;

&lt;p&gt;I would spend 20 to 30 minutes just on this one project. Not because they were grilling me. Because it was a genuine conversation between two people who cared about the problem.&lt;/p&gt;

&lt;p&gt;Here is why it worked: I had lived with it. I hit walls in the rewrite that took days to figure out. The context, the wrong turns, the eventual solution were all stored in my head. When a follow-up question came, the answer was just there.&lt;/p&gt;

&lt;p&gt;You cannot fake that. A first follow-up question exposes a tutorial project immediately. Real work under real constraints creates a depth that no amount of prep can replicate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Side Projects: The Idea Matters More Than the Code
&lt;/h2&gt;

&lt;p&gt;Two side projects that got genuine reactions from interviewers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/FreeShare" rel="noopener noreferrer"&gt;FreeShare&lt;/a&gt;&lt;/strong&gt; - Share files locally without cables or cloud uploads, just two devices on the same network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/Notepage" rel="noopener noreferrer"&gt;NotePage&lt;/a&gt;&lt;/strong&gt; - A browser scratchpad with no login, no account. Open, type, share the URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither is technically complex. A senior developer could build both in a weekend. Interviewers were still surprised by them, not because of the code but because of the idea and how I talked about it.&lt;/p&gt;

&lt;p&gt;When I described FreeShare I did not say "it is a file sharing app." I told the actual story. The frustrating afternoon where every option for moving a file felt ridiculous. The first version that almost worked. The moment it finally clicked. People who tried it and what they said.&lt;/p&gt;

&lt;p&gt;That story has a shape. It is interesting to listen to.&lt;/p&gt;

&lt;p&gt;Most developers build side projects to pad a resume and it shows. Another todo app, another weather dashboard, another tutorial clone. Nothing wrong with it for learning, but in an interview you want something you can talk about like a person, not something you are reading off a bullet point.&lt;/p&gt;

&lt;p&gt;A todo app with no story is just a todo app.&lt;/p&gt;




&lt;h2&gt;
  
  
  Public Presence Is Free and Almost Nobody Bothers
&lt;/h2&gt;

&lt;p&gt;I write on &lt;a href="https://dev.to/varshithvhegde"&gt;dev.to&lt;/a&gt; and keep my GitHub reasonably active. I mentioned both in interviews and things I did not expect happened.&lt;/p&gt;

&lt;p&gt;One interviewer was scrolling through my GitHub mid-call and stopped at a repo I had almost forgotten about. Another had read one of my posts before we even spoke.&lt;/p&gt;

&lt;p&gt;I am not saying build a personal brand. I am saying most developers have nothing publicly visible. No repos worth looking at, no writing, nothing. The bar for standing out here is genuinely low because almost nobody bothers.&lt;/p&gt;

&lt;p&gt;A consistent GitHub history does not need to be perfect. It just needs to show that you actually code outside of work. A few blog posts show you can explain technical ideas clearly. Both of these are signals interviewers notice even when they do not say so explicitly.&lt;/p&gt;

&lt;p&gt;You do not need to go viral. You just need to exist publicly and be consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Risky Trick I Used In Coding Rounds
&lt;/h2&gt;

&lt;p&gt;This one is controversial. Fair warning.&lt;/p&gt;

&lt;p&gt;During coding interviews I would sometimes make a small intentional mistake. Not a logic error. Something tiny. A variable name that does not match. An edge case I "forgot" briefly. Then I would catch it myself, out loud, fix it in 10 seconds, and keep going.&lt;/p&gt;

&lt;p&gt;Why? Because debugging under pressure is a real skill and most interviews never test it. If you write perfect code silently and it works, the interviewer learns you can solve that specific problem. If you catch your own mistake mid-explanation, you are showing how your brain works when things go wrong. That is more valuable information.&lt;/p&gt;

&lt;p&gt;The rule is simple though: this only works after you have already made a strong impression. I only did this late in interviews after the project discussion had gone well. If you are already struggling and you add a visible mistake on top, it makes things worse.&lt;/p&gt;

&lt;p&gt;It worked for me across multiple interviews. I have not seen anyone else write about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DSA Situation, Honestly
&lt;/h2&gt;

&lt;p&gt;I am not great at DSA. I practiced enough to not embarrass myself. Medium-level problems, common patterns, just enough to stay in the conversation.&lt;/p&gt;

&lt;p&gt;What actually moved the needle in coding rounds was never being silent.&lt;/p&gt;

&lt;p&gt;When I write code in an interview I talk constantly. I explain why I am choosing an approach before I write it. I mention complexity. I say out loud when I am unsure about something and then work through it while talking. If I am stuck I say I am stuck and start reasoning from what I do know.&lt;/p&gt;

&lt;p&gt;Interviewers are not just checking if you get the right answer. They are also asking themselves whether they want to work with you. Silent candidates who eventually produce correct code are hard to read. Candidates who think out loud are a known quantity. You can picture working with them.&lt;/p&gt;

&lt;p&gt;The gap you can close by communicating well during a coding round is bigger than most people realize. Especially if you are not going to be the best algorithmic problem solver in the room.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Got Me the Offers
&lt;/h2&gt;

&lt;p&gt;The 1,200 applications were necessary. The DSA practice was necessary. But neither of those converted interviews into offers.&lt;/p&gt;

&lt;p&gt;What did it was having things I could talk about for 30 minutes without running out. A Rust rewrite I genuinely lived through. Side projects born from real frustration. Two years of public writing and code that showed I had been at this consistently.&lt;/p&gt;

&lt;p&gt;When someone asks about something you truly built and care about, you do not prepare an answer. The answer is just there.&lt;/p&gt;

&lt;p&gt;You cannot shortcut that. You can only start building it now so the material exists whenever your job search comes.&lt;/p&gt;

&lt;p&gt;Work on things you can defend. The rest follows.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Writing this because I spent a lot of time during my search wishing someone had written it for me. If you are in the middle of a switch or planning one, drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Got Sick of Miro Eating 10 Minutes of Every Retro. So I Built a Corkboard for the Web.</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Thu, 04 Jun 2026 02:32:36 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/i-got-sick-of-miro-eating-10-minutes-of-every-retro-so-i-built-a-corkboard-for-the-web-41n9</link>
      <guid>https://dev.to/varshithvhegde/i-got-sick-of-miro-eating-10-minutes-of-every-retro-so-i-built-a-corkboard-for-the-web-41n9</guid>
      <description>&lt;p&gt;Here's a thing that happens on every team I've been on.&lt;/p&gt;

&lt;p&gt;Sprint ends. Someone schedules the retro. Someone else shares a Miro link in Slack. Half the team opens it and immediately hits some kind of wall. "I'm on the viewer plan." "It's not loading for me." "How do I add a sticky note again?" One person accidentally deletes the entire frame. Another person is still zoomed into the wrong corner of the board and can't figure out how to get back.&lt;/p&gt;

&lt;p&gt;We spend the first ten to fifteen minutes of every retro just fixing the board.&lt;/p&gt;

&lt;p&gt;And the whole time I'm sitting there thinking: this is a sticky note board. This is the most ancient, simple, obvious tool in the history of meetings. How did we end up needing a tutorial to use it?&lt;/p&gt;

&lt;p&gt;So I built something. It's called PostItUp. It's a real-time collaborative sticky note board that runs in the browser. It looks like an actual physical corkboard. Anyone can drop a note without creating an account. And the whole thing runs for free.&lt;/p&gt;

&lt;p&gt;I want to walk you through all of it. The product, the design decisions, the technical choices, the bugs that nearly broke me, and where this is going. This is going to be a long one. Grab something to drink.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Thing Actually Is
&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%2Frt14ten37jjhek4w5mse.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%2Frt14ten37jjhek4w5mse.png" alt="PostItUp landing page" width="799" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pitch is simple. You create a board. You share a link. People click the link and start posting notes immediately. No signup, no tutorial, no onboarding flow.&lt;/p&gt;

&lt;p&gt;Each note is a sticky. You pick a color, type something, optionally add your name, and hit "Pin it". The note appears on the canvas. Everyone watching the board sees it appear in real time.&lt;/p&gt;

&lt;p&gt;That's the core loop. Everything else is details on top of that.&lt;/p&gt;

&lt;p&gt;The visual design is intentional and it matters more than it looks. Every card has a wobbly hand-drawn border. Notes have little washi tape strips holding them to the board. Push pins mark the project cards. The background is either a dot grid, a ruled notebook page, or a grid pattern depending on what you choose. The fonts are actual handwriting fonts that stay legible at small sizes.&lt;/p&gt;

&lt;p&gt;I made it look this way on purpose. I'll explain why in a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Anyone Would Make It Look Like This
&lt;/h2&gt;

&lt;p&gt;Most collaboration tools look like a SaaS dashboard. Clean, flat, efficient, slightly cold. There's nothing wrong with that for serious project management.&lt;/p&gt;

&lt;p&gt;But for quick feedback sessions and retros and brainstorming, that visual language is actively working against you. It signals "professional context" in a way that makes people more measured and careful with what they write. The same people who would stick a brutally honest Post-it on a physical board will write something much more diplomatic in a Jira ticket.&lt;/p&gt;

&lt;p&gt;Physical sticky notes feel disposable. Throwaway. Safe to be honest. I wanted the digital version to carry that same feeling.&lt;/p&gt;

&lt;p&gt;There's also something to be said for tools that just look different. When you're staring at the same Notion document or the same Miro board all week, you get a little numb to them. Opening something that looks like a corkboard on your screen creates a tiny mental context switch. It's a small thing but I think it matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three Canvas Modes Because One Was Never Going to Work for Everyone
&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%2Fkj94qqqtan9bxx9q9745.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%2Fkj94qqqtan9bxx9q9745.png" alt="New board creation page" width="799" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you create a board you pick one of three canvas modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free canvas&lt;/strong&gt; is the open dot-grid. Notes land wherever you put them. You pan with alt+drag or middle mouse, zoom with ctrl+scroll. Notes can go anywhere. It's the most flexible and also the most chaotic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grid mode&lt;/strong&gt; snaps everything to a 32-pixel grid automatically. The canvas is still completely open but your notes line up without you manually trying to align them. This is what I use by default now. Structured enough to stay readable, free enough to not feel constrained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruled lines&lt;/strong&gt; puts horizontal notebook lines across the background. The whole mood of the board changes. Works really well for sequential feedback or when you're collecting ordered lists rather than freeform ideas.&lt;/p&gt;

&lt;p&gt;The board owner sets the mode at creation time and it applies to the whole canvas. Same codebase, same components, three completely different feels.&lt;/p&gt;

&lt;p&gt;I spent more time on this decision than I expected to. My first instinct was "just do free canvas, that's the obvious choice". But then I ran a couple of quick feedback sessions with it and kept noticing that people who were less comfortable with open canvases kept adding notes awkwardly, unsure of where to put them. Grid mode fixed that completely. And ruled lines came from someone saying they wanted it to feel more like a questionnaire.&lt;/p&gt;

&lt;p&gt;Features should come from watching people use the thing, not from imagining what they might want.&lt;/p&gt;




&lt;h2&gt;
  
  
  Posting a Note
&lt;/h2&gt;

&lt;p&gt;Double-click anywhere on the canvas. A modal appears. Type something. Pick a color. Add your name if you want (it saves your preference in localStorage so you only type it once). Hit "Pin it".&lt;/p&gt;

&lt;p&gt;The note appears on the canvas.&lt;/p&gt;

&lt;p&gt;If someone else is watching the same board, they see it appear right then. No refresh. The board is live.&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%2F4kz1m4ct01jghn89ooe8.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%2F4kz1m4ct01jghn89ooe8.png" alt="Board canvas with notes" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notes have a slight random rotation when they land. Between negative three and positive three degrees, picked randomly at insert time and stored in the database. It's a tiny detail but it makes the board look like notes placed by humans rather than software. Uniformly straight sticky notes on a corkboard would look wrong, so they don't.&lt;/p&gt;

&lt;p&gt;You can drag notes around the canvas. The position saves to the database the moment you let go. Anyone watching sees them move.&lt;/p&gt;

&lt;p&gt;You can upvote notes you agree with. One vote per device per note. The vote count lives on the note permanently.&lt;/p&gt;

&lt;p&gt;Board owners can delete any note. Authors can delete their own notes. The owner can also clear the whole board from settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Part I Think Developers Will Actually Use
&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%2F4hbldzoelpwlpppm8746.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%2F4hbldzoelpwlpppm8746.png" alt="Embed panel in board settings" width="800" height="1856"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every board has an embed panel. You open it from the toolbar using the link icon button. Three options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iFrame&lt;/strong&gt; is the obvious one. One line, your board is embedded in any webpage.&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;iframe&lt;/span&gt; 
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://postitup.varshithvhegde.in/embed/your-board-slug"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; 
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600"&lt;/span&gt; 
  &lt;span class="na"&gt;frameborder=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There's a separate &lt;code&gt;/embed/[slug]&lt;/code&gt; route that renders a stripped-down version of the canvas with no navigation or app chrome. Just the board. Supabase Realtime is still running in there so it updates live inside the iframe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Script tag&lt;/strong&gt; is the one I actually think is useful. Drop this into any webpage:&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;src=&lt;/span&gt;&lt;span class="s"&gt;"https://postitup.varshithvhegde.in/embed.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-board=&lt;/span&gt;&lt;span class="s"&gt;"your-board-slug"&lt;/span&gt;
  &lt;span class="na"&gt;data-url=&lt;/span&gt;&lt;span class="s"&gt;"https://postitup.varshithvhegde.in"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&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;That injects a floating "Leave a note" button in the corner. Click it and a slide-out drawer opens with the full board inside. The iframe only loads when someone actually clicks the button. If nobody opens the drawer, the board costs you nothing. No network request, no layout shift, nothing.&lt;/p&gt;

&lt;p&gt;The script is completely self-contained. No framework required on your end. It works on a static HTML page, a WordPress blog, a Next.js app, whatever. I've already used it on a few pages and the whole setup takes about ninety seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React component&lt;/strong&gt; is what's coming next.&lt;/p&gt;

&lt;p&gt;Right now the snippet shows you how you'd use it once it's published. The npm package isn't out yet but it's in progress. The goal is something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostItBoard&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;postitup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostItBoard&lt;/span&gt;
  &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-board-slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://postitup.varshithvhegde.in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;TypeScript types, SSR-safe, works in Next.js without hydration issues, theming props so it doesn't look foreign inside your app. If you'd use this, watch the repo. It's coming.&lt;/p&gt;


&lt;h2&gt;
  
  
  Now the Technical Part
&lt;/h2&gt;

&lt;p&gt;Stack: Next.js 16 with the App Router, Supabase for database and real-time and auth, TypeScript everywhere, Tailwind for layout utilities. No component library. Everything visual is hand-rolled CSS.&lt;/p&gt;
&lt;h3&gt;
  
  
  How the wobbly borders work
&lt;/h3&gt;

&lt;p&gt;The hand-drawn look on cards comes from a single SVG filter defined in the page layout:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"roughen"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;feTurbulence&lt;/span&gt; 
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"fractalNoise"&lt;/span&gt; 
    &lt;span class="na"&gt;baseFrequency=&lt;/span&gt;&lt;span class="s"&gt;"0.04"&lt;/span&gt; 
    &lt;span class="na"&gt;numOctaves=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; 
    &lt;span class="na"&gt;seed=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt; 
    &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"noise"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;feDisplacementMap&lt;/span&gt; 
    &lt;span class="na"&gt;in=&lt;/span&gt;&lt;span class="s"&gt;"SourceGraphic"&lt;/span&gt; 
    &lt;span class="na"&gt;in2=&lt;/span&gt;&lt;span class="s"&gt;"noise"&lt;/span&gt; 
    &lt;span class="na"&gt;scale=&lt;/span&gt;&lt;span class="s"&gt;"2.5"&lt;/span&gt; 
    &lt;span class="na"&gt;xChannelSelector=&lt;/span&gt;&lt;span class="s"&gt;"R"&lt;/span&gt; 
    &lt;span class="na"&gt;yChannelSelector=&lt;/span&gt;&lt;span class="s"&gt;"G"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The displacement map shifts pixels based on fractal noise. The result looks like someone drew the border by hand. Apply it to any element with &lt;code&gt;filter: url(#roughen)&lt;/code&gt; and it gets the wobbly look. One filter, defined once, referenced everywhere.&lt;/p&gt;

&lt;p&gt;The washi tape strips are semi-transparent divs with a repeating linear gradient for texture:&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;background-image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;repeating-linear-gradient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;transparent&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;18&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;3&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;rgba&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;255&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="err"&gt;18&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="err"&gt;4&lt;/span&gt;&lt;span class="nt"&gt;px&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;mix-blend-mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Mix-blend-mode multiply makes them look translucent against whatever is behind them, the same way real tape behaves on paper. These tiny things add up.&lt;/p&gt;
&lt;h3&gt;
  
  
  Real-time in Supabase
&lt;/h3&gt;

&lt;p&gt;Supabase Realtime is a WebSocket layer on top of Postgres. You subscribe to change events on a table with filters, and Supabase sends you the payloads when rows change.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`board:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;board&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="s2"&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres_changes&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`board_id=eq.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;board&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="s2"&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;payload&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="nf"&gt;setNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;n&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="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;  &lt;span class="c1"&gt;// already have it from optimistic update&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Note&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres_changes&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;payload&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="nf"&gt;setNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;x&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;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres_changes&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;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE&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="nx"&gt;payload&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="nf"&gt;setNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;old&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The INSERT handler checks if the note already exists before adding it. When you post a note, your own UI updates immediately (optimistic update). The real-time event arrives a moment later. Without the check you'd see the note twice.&lt;/p&gt;
&lt;h3&gt;
  
  
  The drag bug that wasted a whole afternoon
&lt;/h3&gt;

&lt;p&gt;Dragging notes worked visually. Positions were not saving correctly. The note would jump back to where it started when you reloaded the page.&lt;/p&gt;

&lt;p&gt;The problem was a stale closure. The mouseup handler was reading the note's position from a React state snapshot that existed when the callback was first created, not the current position after dragging.&lt;/p&gt;

&lt;p&gt;React state updates are asynchronous. By the time mouseup fires, the state you close over when creating the handler can be many renders behind. The note looks like it moved on screen but the value you're saving to the database is the old one.&lt;/p&gt;

&lt;p&gt;The fix is to store the live position in a ref that gets updated on every mousemove:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dragging&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;ox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;      &lt;span class="c1"&gt;// original position&lt;/span&gt;
  &lt;span class="na"&gt;oy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;  &lt;span class="c1"&gt;// mouse start&lt;/span&gt;
  &lt;span class="na"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="na"&gt;finalX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;  &lt;span class="c1"&gt;// updated every mousemove&lt;/span&gt;
  &lt;span class="na"&gt;finalY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// in onMouseMove:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ox&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&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;ny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt;
&lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ny&lt;/span&gt;
&lt;span class="nf"&gt;setNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ns&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;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ny&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// in onMouseUp:&lt;/span&gt;
&lt;span class="kd"&gt;const&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;finalX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalY&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;
&lt;span class="nx"&gt;dragging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;  &lt;span class="c1"&gt;// clear before the async call&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalY&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Refs are mutable and always give you the current value regardless of when the closure was created. The note position in the ref is the actual final position at the time mouseup fires. Problem solved.&lt;/p&gt;
&lt;h3&gt;
  
  
  Row Level Security and the upvote problem
&lt;/h3&gt;

&lt;p&gt;Supabase uses Postgres Row Level Security. Policies on every table control what each user can read, insert, update, and delete. Skip this and your database is open to anyone who gets your anon key, which is embedded in your frontend bundle and completely public.&lt;/p&gt;

&lt;p&gt;Most of the policies are straightforward. The upvote one was not.&lt;/p&gt;

&lt;p&gt;I needed to stop clients from directly setting the upvotes column to any arbitrary number. My first attempt was a &lt;code&gt;with check&lt;/code&gt; constraint that compared the column value against a subquery back into the notes table:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"update notes"&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
  &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&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 caused infinite recursion. Postgres tried to evaluate the policy. The policy read from the notes table. Reading from the notes table triggered the policy. Which read from the notes table again. Stack overflow at the database level.&lt;/p&gt;

&lt;p&gt;The actual solution was a &lt;code&gt;SECURITY DEFINER&lt;/code&gt; function that owns the entire upvote operation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;increment_upvote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voter_fp&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
&lt;span class="k"&gt;security&lt;/span&gt; &lt;span class="k"&gt;definer&lt;/span&gt;
&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;
  &lt;span class="n"&gt;already_voted&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;new_count&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;note_votes&lt;/span&gt; 
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;note_votes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;increment_upvote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;voter_fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;voter_fp&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;already_voted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;already_voted&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'reason'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'already_voted'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;note_votes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voter_fingerprint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;increment_upvote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voter_fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;increment_upvote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;note_id&lt;/span&gt;
  &lt;span class="n"&gt;returning&lt;/span&gt; &lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;new_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json_build_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'upvotes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;SECURITY DEFINER&lt;/code&gt; means the function runs with the database owner's permissions, not the caller's. Direct inserts into note_votes are blocked at the policy level. The only way to register a vote is to call this function. The function checks for duplicate votes and increments atomically. Nobody can manipulate the upvotes column directly from the client.&lt;/p&gt;
&lt;h3&gt;
  
  
  How positions save to the database
&lt;/h3&gt;

&lt;p&gt;Every note has x, y, width, and rotation columns. These are floats. When you drag a note and let go, one database update fires with the new coordinates. When someone else is watching the board, Supabase Realtime delivers the UPDATE event and the note moves on their screen.&lt;/p&gt;

&lt;p&gt;The rotation is set once at insert time. A random value between negative three and positive three degrees, stored permanently. It never changes after that. This is what makes the board look like a real corkboard rather than a grid.&lt;/p&gt;

&lt;p&gt;For grid mode, coordinates snap to the nearest 32-pixel increment before saving:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mode&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;return&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;GRID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;GRID&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Free mode and ruled mode skip the snap entirely. The function is pure, called in the mousemove handler for live preview and again before the database write to make sure what you see is what gets saved.&lt;/p&gt;
&lt;h3&gt;
  
  
  Input validation on both sides
&lt;/h3&gt;

&lt;p&gt;Everything goes through a sanitizer before touching the database:&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;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeText&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;input&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="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;/g&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="c1"&gt;// strip HTML tags&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="sr"&gt;/javascript:/gi&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="c1"&gt;// kill JS URIs&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Length limits are enforced in onChange handlers so you can't even attempt to submit something too long. And then as a final backstop, the database has column-level constraints:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;
  &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;notes_content_length&lt;/span&gt; 
    &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;char_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;between&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;notes_upvotes_nonneg&lt;/span&gt; 
    &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upvotes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If someone bypasses the frontend entirely and sends raw API requests, the database rejects anything that violates these. Two layers, independently enforced.&lt;/p&gt;
&lt;h3&gt;
  
  
  The GDPR stuff
&lt;/h3&gt;

&lt;p&gt;I want this to be something people can trust. So I built proper data rights in from the start rather than adding them later when it's annoying.&lt;/p&gt;

&lt;p&gt;Two Postgres functions do the work.&lt;/p&gt;

&lt;p&gt;Data export returns everything we hold about you as JSON. The account page downloads it as a file. One click, you have your data. This satisfies GDPR Article 20.&lt;/p&gt;

&lt;p&gt;Account deletion is more involved. It needs to delete all your boards (notes cascade via foreign key), anonymise any notes you posted on other people's boards (content stays, attribution is removed), delete your profile, then delete the auth record. The last step requires elevated permissions, so the function runs as SECURITY DEFINER. After the database operations, the client clears localStorage and signs out.&lt;/p&gt;

&lt;p&gt;Total wipe. Nothing left.&lt;/p&gt;


&lt;h2&gt;
  
  
  Auth
&lt;/h2&gt;

&lt;p&gt;GitHub OAuth via Supabase. Email and password if you prefer.&lt;/p&gt;

&lt;p&gt;A Postgres trigger creates a profile record automatically when someone signs up:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle_new_user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw_user_meta_data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'anonymous'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;conflict&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="k"&gt;nothing&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;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt; &lt;span class="k"&gt;definer&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;on conflict do nothing&lt;/code&gt; stops errors if the trigger somehow fires twice for the same user. This happened during testing more than once so I'm glad it's there.&lt;/p&gt;

&lt;p&gt;Route protection is a Next.js middleware that checks Supabase session before serving protected pages. If there's no session, you get redirected to login with your intended destination as a query parameter. After signing in you land where you meant to go.&lt;/p&gt;

&lt;p&gt;Anonymous users can post on public and link-only boards with no account at all. Their notes show an author name from localStorage. Their votes are tracked by a random fingerprint also from localStorage. Nothing tied to an identity. Nothing in the database except the note itself.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Boards: How the Full Flow Works
&lt;/h2&gt;

&lt;p&gt;You create a board on &lt;code&gt;/new&lt;/code&gt;. Title, description, a prompt for contributors, canvas mode, visibility. Submit and you get redirected to &lt;code&gt;/board/your-slug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The slug is generated from the board title with a four-character random suffix to prevent collisions. &lt;code&gt;sprint-retro-3a7f&lt;/code&gt; instead of just &lt;code&gt;sprint-retro&lt;/code&gt;. Simple and human-readable.&lt;/p&gt;

&lt;p&gt;The board page is a Next.js server component that fetches the board data and initial notes server-side. This matters for performance: when the page loads the canvas is already populated. No loading spinner. No empty board that fills in after a moment. The notes are in the HTML.&lt;/p&gt;

&lt;p&gt;After that initial load, Supabase Realtime takes over and handles all subsequent updates. Two different systems, each doing exactly what they're good at.&lt;/p&gt;

&lt;p&gt;Board settings let owners update the title, description, prompt, canvas mode, and visibility. Changing mode from free to grid doesn't move any existing notes, it just starts snapping new ones. Changing visibility takes effect immediately.&lt;/p&gt;

&lt;p&gt;Deleting a board requires typing the board title to confirm. All notes cascade-delete. Irreversible. The confirmation requirement is annoying on purpose.&lt;/p&gt;


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

&lt;p&gt;The npm package is the thing I'm most focused on right now.&lt;/p&gt;

&lt;p&gt;The iframe embed works well but dropping a component directly into your app is a much cleaner experience when you're already in a React codebase. The plan is a &lt;code&gt;&amp;lt;PostItBoard /&amp;gt;&lt;/code&gt; with a TypeScript API, theming props so it doesn't look foreign in your UI, and SSR safety so it works in Next.js without hydration warnings.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PostItBoard&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;postitup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostItBoard&lt;/span&gt;
  &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-board-slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://postitup.varshithvhegde.in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;paper&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the shape of it. Publishing to npm soon. Watch the GitHub repo if you'd use this.&lt;/p&gt;

&lt;p&gt;After that: &lt;strong&gt;lane mode&lt;/strong&gt;. Columns. Kanban-style layout so you can have things like Liked / Meh / Disliked for product feedback sessions, or What Went Well / What Didn't / Action Items for retros. Same real-time sync, same anonymous posting, just organized into columns instead of a free canvas.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;board templates&lt;/strong&gt; so you're not starting from scratch every time you want to run a retro.&lt;/p&gt;


&lt;h2&gt;
  
  
  Free. Actually Free.
&lt;/h2&gt;

&lt;p&gt;The whole thing runs on Supabase free tier and Vercel hobby plan.&lt;/p&gt;

&lt;p&gt;Supabase free: 500MB database, 50,000 monthly active users, unlimited API requests.&lt;br&gt;&lt;br&gt;
Vercel hobby: unlimited deployments, free domain, fast edge network.&lt;/p&gt;

&lt;p&gt;You're not going to hit those limits running retros with your team.&lt;/p&gt;

&lt;p&gt;This is the same lesson as the FormRelay thing I wrote about a while back. There's this huge gap between "run your own servers" and "pay $20 a month for something that's really just a database insert". An embarrassing number of problems that cost real money every month are actually just weekend projects in disguise.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/varshithvhegde/i-built-a-form-backend-in-a-weekend-because-paying-20month-for-contact-forms-is-stupid-1o34" class="crayons-story__hidden-navigation-link"&gt;I Built a Form Backend in a Weekend Because Paying $20/Month for Contact Forms is Stupid&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/varshithvhegde" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885064%2F4ab304f4-a3f3-409c-8217-9ce130e57c18.jpeg" alt="varshithvhegde profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/varshithvhegde" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Varshith V Hegde
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Varshith V Hegde
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3136225" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/varshithvhegde" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F885064%2F4ab304f4-a3f3-409c-8217-9ce130e57c18.jpeg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Varshith V Hegde&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/varshithvhegde/i-built-a-form-backend-in-a-weekend-because-paying-20month-for-contact-forms-is-stupid-1o34" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 30 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/varshithvhegde/i-built-a-form-backend-in-a-weekend-because-paying-20month-for-contact-forms-is-stupid-1o34" id="article-link-3136225"&gt;
          I Built a Form Backend in a Weekend Because Paying $20/Month for Contact Forms is Stupid
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/saas"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;saas&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/varshithvhegde/i-built-a-form-backend-in-a-weekend-because-paying-20month-for-contact-forms-is-stupid-1o34" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;71&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/varshithvhegde/i-built-a-form-backend-in-a-weekend-because-paying-20month-for-contact-forms-is-stupid-1o34#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              52&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;A sticky note board that updates in real time sounds complicated. It isn't. It's a database table, a WebSocket subscription, and a canvas that knows how to drag things around. Total code across the meaningful files is maybe two thousand lines. You could read the whole repo in an afternoon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Live: &lt;strong&gt;&lt;a href="https://postitup.varshithvhegde.in" rel="noopener noreferrer"&gt;postitup.varshithvhegde.in&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;strong&gt;&lt;a href="https://github.com/Varshithvhegde/postitup" rel="noopener noreferrer"&gt;github.com/Varshithvhegde/postitup&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Varshithvhegde" rel="noopener noreferrer"&gt;
        Varshithvhegde
      &lt;/a&gt; / &lt;a href="https://github.com/Varshithvhegde/postitup" rel="noopener noreferrer"&gt;
        postitup
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Collaborative sticky note boards — embeddable, real-time, paper-aesthetic
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Varshithvhegde/postitup/blog/screenshots/postitupbg_cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVarshithvhegde%2Fpostitup%2FHEAD%2Fblog%2Fscreenshots%2Fpostitupbg_cover.png" alt="PostItUp Cover"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;PostItUp 📌&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;A real-time collaborative sticky note board for the web. Paper aesthetic, anonymous posting, embeddable anywhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://postitup.varshithvhegde.in" rel="nofollow noopener noreferrer"&gt;postitup.varshithvhegde.in&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it is&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;PostItUp lets you create a board, share a link, and anyone can drop a sticky note in real time — no account required. Think retros, feedback sessions, brainstorming, or just collecting ideas from a group.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Varshithvhegde/postitup/blog/screenshots/landing.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVarshithvhegde%2Fpostitup%2FHEAD%2Fblog%2Fscreenshots%2Flanding.png" alt="Landing Page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three canvas modes&lt;/strong&gt; — free-drag, grid snap, or ruled lines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time sync&lt;/strong&gt; — notes appear live via Supabase Realtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous posting&lt;/strong&gt; — no signup needed on public/link boards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Star ratings&lt;/strong&gt; — boards can optionally collect 1-5 star reviews shown as cards on the canvas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drag notes&lt;/strong&gt; — reposition any note, position saves instantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upvotes&lt;/strong&gt; — thumbs up notes you agree with (one per device)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embeddable&lt;/strong&gt; — iframe, script tag, or React component&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GDPR compliant&lt;/strong&gt; — data export and full account deletion built in&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Creating a board&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Varshithvhegde/postitup/blog/screenshots/new-board.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FVarshithvhegde%2Fpostitup%2FHEAD%2Fblog%2Fscreenshots%2Fnew-board.png" alt="New Board"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pick a canvas mode, set visibility…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Varshithvhegde/postitup" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Create a board. Share the link with someone. Watch notes appear in real time.&lt;/p&gt;

&lt;p&gt;If something is broken or you have a feature idea, open an issue. If you want to contribute, PRs are open. MIT licensed so do whatever you want with it.&lt;/p&gt;

&lt;p&gt;And if you end up using the embed somewhere, I'd genuinely love to see it. Drop it in the comments or email me at &lt;a href="mailto:varshithvh@gmail.com"&gt;varshithvh@gmail.com&lt;/a&gt;.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Agent Gateways Are Coming — Here Are the First 6 Platforms Building Them (2026)</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Sat, 16 May 2026 08:12:14 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/agent-gateways-are-coming-here-are-the-first-6-platforms-building-them-2026-pfj</link>
      <guid>https://dev.to/varshithvhegde/agent-gateways-are-coming-here-are-the-first-6-platforms-building-them-2026-pfj</guid>
      <description>&lt;p&gt;Something shifted in April 2026. In the span of about ten days, Kong announced agent gateway support, Databricks folded agent governance into its AI platform, and the Linux Foundation formally accepted the AgentGateway project under its Agentic AI Foundation. None of these organizations talked to each other before shipping. That kind of parallel movement usually means a category is crystallizing.&lt;/p&gt;

&lt;p&gt;I have been thinking about agent gateways for a while now, partly because I keep running into the same problem at work. We have multiple agents in production. Each one talks to different LLMs, different MCP servers, different internal APIs. Nobody can answer the basic question: "if one of these agents does something wrong tonight, how do we know, and how do we stop it?" That question is what an agent gateway is supposed to answer.&lt;/p&gt;

&lt;p&gt;The category is genuinely new. Unlike MCP gateways or LLM gateways, which solve narrower problems, an agent gateway tries to be the full control plane for an agentic estate: where agents are registered, how their identity is managed, what tools they can reach, how their traffic is governed across LLM routing and MCP tool access, and what the audit trail looks like after the fact. Think of it as what Istio did for microservices, but pointed at autonomous agents.&lt;/p&gt;

&lt;p&gt;This is what I found when I looked at who is actually building this category right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Agent Gateway Category Is Different
&lt;/h2&gt;

&lt;p&gt;Before getting into the platforms, it is worth being precise about what makes an agent gateway distinct from the other gateway categories you may already be running.&lt;/p&gt;

&lt;p&gt;An LLM gateway manages traffic to language models: routing, cost control, fallback, logging. That is useful and most teams need it. An MCP gateway governs access to tools: which agents can call which MCP servers, with what permissions. Also useful, and increasingly necessary as tool surfaces expand.&lt;/p&gt;

&lt;p&gt;An agent gateway tries to hold both of those things together and add a third layer: agent-level identity, registration, and observability. An agent is not a stateless API client. It runs for minutes or hours, spawns sub-agents, maintains state across tool calls, and makes decisions that ripple through production systems. The infrastructure that governs it needs to understand that execution model, not just proxy individual HTTP requests.&lt;/p&gt;

&lt;p&gt;That framing lands differently when you have agents actually running in production. Here is what is being built.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. TrueFoundry — The Full-Stack Agent Control Plane
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Organizations that want one control plane for the entire agent infrastructure stack&lt;/strong&gt;&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%2Fbkwcbom190tm2rz1zdtq.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%2Fbkwcbom190tm2rz1zdtq.png" alt="TrueFoundry Agent Gateway: unified control plane showing LLM routing, MCP governance, and agent deployment in one platform" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TrueFoundry is an enterprise AI platform that was named a Representative Vendor in the 2025 Gartner Market Guide for AI Gateways. Its Agent Gateway module is the most complete attempt I have seen to unify all four layers: LLM routing, MCP tool governance, agent deployment, and agent-level observability in one platform. TrueFoundry already processes over 10 billion requests per month and has Fortune 1000 companies using it in production. SOC 2, HIPAA, and ITAR certifications are in place. VPC, on-premises, and air-gapped deployments are all supported.&lt;/p&gt;

&lt;p&gt;What TrueFoundry is doing with agents specifically is worth spending a moment on. The Agent Gateway module registers agents as first-class infrastructure objects, not just API clients. You can assign a "Principal" to each agent: a constrained identity that enforces what the agent can do regardless of what prompt instructs it. A production-ready Agent Gateway must serve as the interconnect middleware that standardizes protocols, enforces security policy, and orchestrates the state of execution. TrueFoundry builds that principal object into the data plane itself, not as a policy layer that can be talked around.&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%2F92df27exakn246b821mp.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%2F92df27exakn246b821mp.png" alt="TrueFoundry Agent Gateway architecture: 7-pillar framework showing session management, identity constraints, and multi-agent coordination for enterprise-grade agentic workflows" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The session management piece is also worth noting. Agents pause. They wait for external responses. They resume two days later with the same task context. TrueFoundry's architecture handles session hydration from persistent storage so agent state survives across restarts, scale events, and cloud region transitions. That is a real operational problem that pure-play gateways typically ask you to solve yourself.&lt;/p&gt;

&lt;p&gt;Latency sits at roughly 3 to 4ms overhead, with 350+ requests per second per vCPU. The platform is built in Rust for the data plane, which gives you memory safety alongside throughput. The multi-agent coordination layer is also production-tested — this is not a whitepaper feature.&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%2Fng3hyr5f20p2nrtbj0qy.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%2Fng3hyr5f20p2nrtbj0qy.png" alt="TrueFoundry multi-agent system architecture: showing how the Agent Gateway unifies MCP, A2A, and LLM traffic for enterprise-scale agentic deployments" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent gateway category is being born right now. TrueFoundry is the only Gartner-recognized platform attempting to unify all four layers, LLM routing, MCP tool governance, agent deployment, and agent-level observability, in a single control plane. Most other entries on this list solve one or two of these layers. The question for 2026 is whether enterprises want one vendor for the full stack or best-of-breed at each layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; The agent-specific features are newer than the LLM and MCP gateway components, which have been battle-tested for longer. Adopting TrueFoundry for agent governance means adopting a significant platform. Teams that only need one layer of the stack may find this more than they need right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. AgentGateway.dev (Linux Foundation / AAIF)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Teams who want to bet on open-source and contribute to the emerging agent connectivity standard&lt;/strong&gt;&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%2Fiy1mqvg4872vqmmx30rr.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%2Fiy1mqvg4872vqmmx30rr.png" alt="AgentGateway.dev: open-source Rust-based agent gateway backed by the Linux Foundation, with contributors from Microsoft, Apple, AWS, Adobe, Cisco, Salesforce, and Huawei" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AgentGateway is an open-source, Rust-based project that originated at Solo.io and was donated to the Linux Foundation. Agentgateway is the first and only data plane built from the ground up for AI agents, governing and securing communication across agent-to-agent, agent-to-tool and agent-to-LLM traffic. The project sits under the Linux Foundation's Agentic AI Foundation alongside MCP and OpenAI's AGENTS.md.&lt;/p&gt;

&lt;p&gt;The architecture supports LLM routing, MCP tool federation, and A2A agent-to-agent communication in a single data plane. Contributors include Microsoft, AWS, Cisco, Adobe, Huawei, and Apple. For a project of its age, that contributor list is unusually strong. The policy framework integrates with Open Policy Agent and relationship-based authorization systems for fine-grained, context-aware decisions.&lt;/p&gt;

&lt;p&gt;One concrete use Solo.io has documented: routing all LLM traffic through agentgateway to gain per-user, per-model cost visibility, and using it to govern which MCP tools agents can call without modifying the agents or the MCP servers themselves. That kind of transparent interposition is exactly what a gateway should do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; AgentGateway.dev is a relatively new public release. There is no RBAC in the enterprise governance sense yet, no compliance certifications, and no production case studies from non-contributing organizations. It is a well-designed foundation for what agent gateways will look like in the next few years. You are not ready to run mission-critical agents behind this today if your CISO needs a compliance checkbox, but it is worth watching closely and contributing to if your team has the appetite.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. SnapLogic Agent Gateway
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Organizations that need agent orchestration integrated with enterprise workflow automation&lt;/strong&gt;&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%2F8rfb61b5ikbxvg9pxnr9.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%2F8rfb61b5ikbxvg9pxnr9.png" alt="SnapLogic AI Gateway and Trusted Agent Identity announcement: enterprise agent governance with token propagation, user-level identity, and MCP observability dashboard" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SnapLogic announced its AI Gateway and Trusted Agent Identity features on April 16, 2026, as part of an expansion of its Agentic Integration Platform. The timing is notable because it happened within days of several other agent gateway announcements, which tells you the broader market is moving simultaneously rather than one vendor leading others.&lt;/p&gt;

&lt;p&gt;The thing that stands out about SnapLogic's approach is Trusted Agent Identity. The platform ensures that when an AI agent acts on behalf of a user, it operates with that specific user's identity and permissions, not a shared service account. Under this token propagation model, user identity flows from the agent through the integration layer into backend systems, making each action traceable to the person who initiated it. That is the right architecture for regulated environments where "the agent did it" is not an acceptable audit trail entry.&lt;/p&gt;

&lt;p&gt;SnapLogic's AgentCreator visual builder lets teams construct and deploy agents without writing code, with full visibility into reasoning steps, tool calls, and results at design time. The platform also bridges over 1,000 native connectors to MCP with native bi-directional MCP support, covering ERP, CRM, databases, and SaaS systems. For organizations already using SnapLogic for integration work, adding agent governance through the same platform has real appeal from a consolidation standpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; SnapLogic is primarily an integration platform that added AI agent features, not an infrastructure-native agent gateway. The audience is less the platform engineers building custom agent infrastructure and more the enterprise teams deploying agents through existing integration tooling. Developer experience reflects that priority. Pricing is enterprise-only and requires a conversation with sales.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Pragatix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Regulated industries where agent-level governance and on-premises deployment are the primary concerns&lt;/strong&gt;&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%2F8rtjltr963l4y783aui1.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%2F8rtjltr963l4y783aui1.png" alt="Pragatix AI agent security dashboard showing enterprise agent visibility, governance controls, and execution-layer monitoring for regulated industries" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pragatix is an AI agent governance platform from AGAT Software, focused on execution-layer controls. It supports on-premise and private cloud deployment, which is the hard requirement for a meaningful slice of the enterprise market: healthcare systems, financial services under strict data residency rules, government agencies. The positioning is specifically around regulated industries, and the feature set reflects that.&lt;/p&gt;

&lt;p&gt;The platform combines an AI Firewall layer that governs how AI services are accessed across the enterprise, with discovery and behavioral monitoring at the agent level. Pragatix gives security teams visibility into every AI agent operating across the enterprise, maps agent activity, flags risky behavior, and tracks what agents are doing in real time. For organizations that need to answer "which AI agents are your employees using right now?" before they can even begin governance, that inventory capability is where evaluation starts.&lt;/p&gt;

&lt;p&gt;The Private AI deployment model, supporting air-gapped, private cloud, and full SaaS configurations, addresses one of the most common blockers in regulated enterprise AI adoption: legal and compliance teams often cannot approve cloud-hosted agent governance because they cannot control where audit data goes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; Pragatix is early stage. Public benchmarks and production case studies at scale are limited, which makes independent evaluation harder. The feature set is narrower than full-stack gateway platforms: governance and security focus, not LLM routing or MCP federation. Evaluating it properly requires direct engagement with the team rather than relying on public documentation.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Operant AI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Security teams who need to understand agent attack vectors before deploying governance&lt;/strong&gt;&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%2Fy5iuxtzqkzwjyzkux27z.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%2Fy5iuxtzqkzwjyzkux27z.png" alt="Operant AI Shadow Escape: the first zero-click agentic attack exploiting MCP, documenting how trusted AI agents can silently exfiltrate PII and critical data" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Operant sits in an interesting position on this list: it publishes the most rigorous security research on the agent attack surface while also building a runtime defense platform. Their discovery of "Shadow Escape," a zero-click exploit that weaponizes MCP against trusted agents, is the kind of research that changes how security teams think about agent threat models.&lt;/p&gt;

&lt;p&gt;Shadow Escape shows that the next data breach won't come from a hacker, it will come from a trusted AI agent. Traditional perimeter security cannot stop threats that are already inside the perimeter. The attack exploits MCP to silently exfiltrate PII, medical records, and financial data through what appear to be legitimate agent sessions, invisible to both users and conventional security tooling.&lt;/p&gt;

&lt;p&gt;Operant is featured across six of Gartner's critical AI security reports, including the MCP Gateways Innovation Insight and AI TRiSM Market Guide. The platform includes inline PII redaction, dynamic reputation scoring for MCP servers, and real-time threat detection that understands agent tool call semantics rather than treating them as generic HTTP traffic. Their Shadow Escape research prompted formal CVE designation and responses from OpenAI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; Operant is security-first, not infrastructure-first. Routing, observability, and general governance capabilities are narrower than the enterprise-focused platform options. Most security teams I have talked to pair Operant with one of the other options on this list rather than using it as a standalone agent gateway. Think of it as defense-in-depth alongside a primary control plane, not a replacement for one.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Obot AI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for: Teams whose specific pain is "we have dozens of MCP servers and no governance over any of them"&lt;/strong&gt;&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%2Facob2riotwbuc7dqunwt.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%2Facob2riotwbuc7dqunwt.png" alt="Obot MCP Gateway: open-source platform providing MCP hosting, registry, gateway, and chat client for enterprise MCP server lifecycle management and secure agent access" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obot is an open-source MCP gateway combined with agent orchestration features. It covers the full MCP lifecycle: hosting, registry, gateway, and a standards-compliant chat client. The v0.14 release brought MCP Registry Support, letting organizations control exactly which MCP servers users can see and install across VS Code, GitHub Copilot, and other MCP-enabled clients.&lt;/p&gt;

&lt;p&gt;Obot also donated the MCP Dev Summit to the Linux Foundation's Agentic AI Foundation, which signals a deliberate bet on the open ecosystem rather than the acquihire game. The platform integrates with popular orchestration frameworks like LangGraph and n8n, and clients like ChatGPT and Claude Desktop can leverage MCP servers managed through Obot.&lt;/p&gt;

&lt;p&gt;The architecture is worth understanding: IT deploys the Obot Gateway Server and connects it to the organization's identity provider (GitHub, Okta, Microsoft Entra, etc.). Admins define policies for which teams can access which MCP servers. Employees browse a catalog of approved MCP tools and connect via one-click URLs that drop directly into AI clients. Every request passes through Obot's proxy layer for a unified audit trail. Secrets live in a shim layer alongside each server container and are never exposed to the MCP server itself.&lt;/p&gt;

&lt;p&gt;Obot recently refactored its gateway from an intercepting server model to a composable infrastructure model, with a reverse-proxy passthrough at the core and a protocol-aware shim handling authorization and audit. That architectural decision is the right call for long-term maintainability and extensibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; Obot is primarily MCP-focused, not a full agent gateway in the sense of covering LLM routing and A2A protocol support. Governance and compliance features require more DIY configuration than commercial alternatives. The operational burden sits with your team. For organizations that specifically need MCP server lifecycle management with some agent orchestration on top, the fit is strong. For organizations that need the full agent control plane, Obot covers one important layer of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;LLM Routing&lt;/th&gt;
&lt;th&gt;MCP Governance&lt;/th&gt;
&lt;th&gt;A2A Support&lt;/th&gt;
&lt;th&gt;Agent Registration&lt;/th&gt;
&lt;th&gt;Self-Hosted&lt;/th&gt;
&lt;th&gt;Compliance Certs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TrueFoundry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (deep)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (VPC/on-prem/air-gapped)&lt;/td&gt;
&lt;td&gt;SOC 2, HIPAA, ITAR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AgentGateway.dev&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SnapLogic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (via MCP)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes (Trusted Agent Identity)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pragatix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (on-prem, private cloud)&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operant AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (security-first)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Gartner-recognized (6 reports)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Obot AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (MCP-focused)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Honest State of the Category
&lt;/h2&gt;

&lt;p&gt;I want to be direct about something: the agent gateway category is where the API gateway category was in 2015. You have a handful of credible options, a clear sense that the problem is real and load-bearing, and a lot of uncertainty about which platforms survive to maturity.&lt;/p&gt;

&lt;p&gt;The reason to pay attention now rather than waiting is that architectural decisions made during this window tend to stick. Which agent framework your teams standardize on, whether agent identity is managed at the infrastructure layer or baked into individual applications, whether your audit trail is unified or scattered across five different systems: those are hard to change once you have dozens of agents in production.&lt;/p&gt;

&lt;p&gt;The platforms that win in this category will be the ones that understand that an agent gateway is not just an MCP gateway with extra steps. It is infrastructure that has to reason about stateful execution, agent identity that persists across sessions, and protocol semantics that traditional gateways were never designed for. Most of the options on this list are early. TrueFoundry is the furthest along as a production-ready, full-stack platform. AgentGateway.dev is the open-source bet with serious institutional backing. SnapLogic is the integration-platform play. Obot is the open-source MCP lifecycle specialist. Pragatix and Operant are solving important but narrower problems in governance and security respectively.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Verdicts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick TrueFoundry&lt;/strong&gt; if you want one control plane for the entire agent infrastructure stack and have a platform team ready to operate it. The Gartner recognition and 10B+ requests per month are real markers of production maturity, and the combination of LLM routing, MCP governance, and agent-level controls in one platform is genuinely rare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick AgentGateway.dev&lt;/strong&gt; if you want open-source and community-driven agent connectivity, your team has the engineering capacity to run ahead of the documentation, and you want to be part of shaping what the standard looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick SnapLogic&lt;/strong&gt; if you need agent orchestration integrated with enterprise workflow automation and your organization is already in the SnapLogic ecosystem. The Trusted Agent Identity feature is a real differentiator for regulated environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Pragatix&lt;/strong&gt; if agent-level governance in regulated industries is your top priority and on-premises deployment is a hard requirement with no negotiation room.&lt;/p&gt;

&lt;p&gt;Operant belongs on every security team's shortlist for threat intelligence and runtime defense, paired with one of the above for broader governance. Obot is a strong open-source option if your specific problem is MCP server lifecycle management rather than the full agent control plane.&lt;/p&gt;

&lt;p&gt;Check back on this list in six months. I expect at least two names from outside this list to be credible options by then, and at least one of the current names to have been acquired.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What are you running in front of your agents right now? If you have production experience with any of these platforms, I am genuinely curious how they hold up in practice. Drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Best MCP Gateways for Enterprise Teams in 2026</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Fri, 08 May 2026 15:25:43 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/best-mcp-gateways-for-enterprise-teams-in-2026-5fk8</link>
      <guid>https://dev.to/varshithvhegde/best-mcp-gateways-for-enterprise-teams-in-2026-5fk8</guid>
      <description>&lt;p&gt;I spent the last few months evaluating MCP gateways for a mid-size financial services client. Their agentic stack had grown organically: one team was using local STDIO servers, another had hand-rolled HTTP wrappers, and nobody had a clear answer when the CISO asked "who can see what tools our agents are calling?" That conversation was the starting gun.&lt;/p&gt;

&lt;p&gt;What followed was several weeks of standing up test environments, reading compliance documentation, and talking to engineers who had actually run these things in production. This writeup is the distilled version of that evaluation.&lt;/p&gt;

&lt;p&gt;Before I get into the comparison, a quick framing note: MCP (Model Context Protocol) has moved surprisingly fast. Anthropic open-sourced it in November 2024, and by early 2026 it had crossed 97 million SDK downloads and was adopted by every major AI vendor. But as &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-mcp-roadmap/" rel="noopener noreferrer"&gt;the official 2026 MCP roadmap openly acknowledges&lt;/a&gt;, the protocol itself still has gaps around audit trails, SSO-integrated auth, gateway behavior, and configuration portability. The gateway layer is where those gaps get filled, and that is exactly why this decision matters.&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%2Foosz0vl6yr99kn3blmdo.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%2Foosz0vl6yr99kn3blmdo.png" alt="MCP Gateway Architecture showing agents connecting to tools through a central control plane" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why You Even Need a Gateway
&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%2Fag8g9j0dsk3uu4pejepu.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%2Fag8g9j0dsk3uu4pejepu.png" alt="Why you need gateway" width="800" height="1013"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The naive architecture is direct connections: each agent talks directly to each tool. That works for demos. It falls apart immediately at enterprise scale because you end up with what engineers call the N x M problem. Ten agents, each needing access to five tools, gives you fifty independent integration points to secure, monitor, and maintain. Nobody has time for that.&lt;/p&gt;

&lt;p&gt;A proper MCP gateway centralizes authentication, authorization, audit logging, and traffic management into a single control plane. It is the difference between knowing what your agents are doing and just hoping they are behaving.&lt;/p&gt;

&lt;p&gt;One framing I found useful: treat MCP servers like production APIs, because that is what they are. Gartner's emerging practices guidance says exactly this, recommending that organizations apply gateway-centric architecture to MCP the same way they would any API surface.&lt;/p&gt;

&lt;p&gt;With that context, here is how the landscape looks right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Contenders
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. TrueFoundry MCP Gateway
&lt;/h3&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%2Fxsuv8v6oe3gvdvhxfxcv.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%2Fxsuv8v6oe3gvdvhxfxcv.png" alt="TrueFoundry MCP" width="799" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Organizations that need MCP governance unified with LLM routing and model deployment in one place&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TrueFoundry is an enterprise AI gateway that was recognized as a Representative Vendor in the 2025 Gartner Market Guide for AI Gateways. It is the only MCP gateway in this list that is part of a broader, Gartner-recognized AI Gateway platform, which matters if you are trying to consolidate your AI infrastructure rather than add another point solution to your stack.&lt;/p&gt;

&lt;p&gt;What makes TrueFoundry genuinely different from everything else I evaluated is the full lifecycle model. Most gateways govern access to MCP servers that you deploy elsewhere. TrueFoundry lets you deploy and host those servers on the same platform. One control plane for deploying tools, governing who can access them, and monitoring how agents use them. No other gateway on this list does that end to end.&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%2F4hjcjpxnaoqx0k3o4q1a.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%2F4hjcjpxnaoqx0k3o4q1a.png" alt="TrueFoundry Ai gateway" width="799" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The platform processes over 10 billion requests per month across Fortune 1000 customers, with a latency overhead of roughly 3 to 4ms. It supports RBAC at a granular level, secret management, and full observability including latency graphs and token-level traces. On the compliance side it holds SOC 2, HIPAA, and ITAR certifications, and you can deploy it inside your own VPC or fully on-premises, which was a hard requirement for my financial services client.&lt;/p&gt;

&lt;p&gt;There is a virtual MCP server abstraction worth calling out. Instead of connecting agents to physical APIs directly, you can aggregate tools into logical endpoints. A "Finance Agent Virtual Server" might expose the BigQuery query tool, a Stripe exchange rate tool, and a Slack alert tool, all through one endpoint. Swapping out a backend implementation later does not require touching agent code. That is a real operational advantage at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; TrueFoundry does not offer a pre-built integration library. You deploy your own MCP servers, which means you need a platform team that can own that. It is also at its best in organizations with real DevOps maturity. If you are a two-person startup, this is probably more platform than you need right now.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. MintMCP
&lt;/h3&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%2Ffh7sxpo0huvecelkhsd9.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%2Ffh7sxpo0huvecelkhsd9.png" alt="MintMCP" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Teams that need SOC 2 compliance out of the box with zero infrastructure to manage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MintMCP is backed by some notable names (Andrej Karpathy, Jeff Dean, and institutional investors including Coatue), and its core value proposition is compliance speed. It is SOC 2 Type II certified with continuous compliance monitoring, and its headline feature is one-click STDIO-to-managed conversion: you take a local MCP server, and MintMCP wraps it with OAuth and audit logging almost instantly.&lt;/p&gt;

&lt;p&gt;For teams that have built a bunch of local STDIO-based MCP servers (which is most of the community, honestly) and need to make them production-ready without rebuilding infrastructure from scratch, MintMCP is genuinely fast to get running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; It is managed-only, so there is no self-hosted option. For regulated industries with data residency requirements, that is often a hard no. It also does not do LLM routing, so you would need a separate tool for model-level governance. And as a younger company, it has less of a production track record at Fortune 1000 scale than TrueFoundry does.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Composio
&lt;/h3&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%2Fjg7xgiivzocb0gmsbatw.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%2Fjg7xgiivzocb0gmsbatw.png" alt="Composio" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Teams whose agents need to connect to dozens of SaaS tools immediately&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Composio takes a different philosophical approach. Rather than building a gateway for infrastructure you deploy, it is a managed integration platform with 850-plus pre-built connectors for tools like Slack, GitHub, Jira, Salesforce, and hundreds of others. Its focus is breadth: get agents connected to the SaaS tools they need as fast as possible.&lt;/p&gt;

&lt;p&gt;The value is real. If you are building an agent that needs to touch ten or fifteen different SaaS products, building and maintaining those connectors yourself is months of work. Composio handles authentication lifecycle, schema drift, malformed payloads, and a lot of the operational overhead that makes integrations annoying in practice. It is also SOC 2 Type II and ISO 27001 certified, and it has RBAC controls at the action level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; Composio is managed-only, no self-hosted option. The governance depth is narrower than enterprise-focused options: it is optimized for breadth of connectivity, not deep policy enforcement. The tools are also closed-source, so if a pre-built connector does not behave exactly the way you need it to, your options are limited. Premium tool calls (semantic search, code execution) run at 3x the standard rate, which can make costs unpredictable at scale.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Docker MCP Gateway
&lt;/h3&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%2Faohm0ajf2988bbrdgoo1.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%2Faohm0ajf2988bbrdgoo1.png" alt="Docker MCP Gateway" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Developers building locally who want container isolation and familiar tooling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docker's approach is container-native: each MCP server runs in its own isolated container with resource limits and cryptographic image signing for supply chain security. If your team lives in Docker and Kubernetes already, the mental model is comfortable. There is real value in the isolation guarantees for local development environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; This is fundamentally a local development tool. There is no production governance: no RBAC, no audit logging, no centralized access control. Scaling to enterprise requires significant DIY effort to bolt on authentication, identity management, and audit infrastructure. I have seen teams try to build production systems on Docker MCP Gateway and end up with a fragile collection of glue code that nobody wants to own.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. MCPJungle
&lt;/h3&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%2Faw8994mqn66kr2nivj90.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%2Faw8994mqn66kr2nivj90.png" alt="MCPJungle AI gateway" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Experimenters who want a simple open-source aggregation layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCPJungle is an open-source MCP gateway focused on aggregation and tool discovery. Setup is simple, which is its main appeal. For individual developers trying to understand how gateway aggregation works before committing to a platform, it is a reasonable starting point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; It is very early stage. Governance features are minimal, documentation is thin, and the community is small. I would not run anything customer-facing on this today.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. IBM ContextForge
&lt;/h3&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%2Frjbfgwoz7h7wr23mta4p.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%2Frjbfgwoz7h7wr23mta4p.png" alt="IBM ContextForge" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Large enterprises with distributed teams needing multi-cluster federation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ContextForge is an open-source, Kubernetes-native MCP gateway with federation built in. Multiple gateway instances auto-discover each other, merge tool registries, and operate as a unified system across regions. It also supports protocol bridging, so legacy REST and gRPC services can be exposed as MCP tools without rewriting them.&lt;/p&gt;

&lt;p&gt;That federation architecture is a genuine differentiator if you are a global enterprise running infrastructure across multiple regions or subsidiaries. IBM's broader enterprise ecosystem integrations are also real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; Setup is complex, designed for organizations with sophisticated DevOps teams. Reported latency sits at 100 to 300ms per operation, which is significantly higher than other options and may be an issue for latency-sensitive workloads. It is also worth noting that ContextForge is a community project, not an officially supported IBM product, so you are largely on your own operationally.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Lasso Security MCP Gateway
&lt;/h3&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%2F6qm8r5zoask8e0yr4kyw.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%2F6qm8r5zoask8e0yr4kyw.png" alt="Lasso Security MCP Gateway" width="799" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for: Teams where threat prevention is the primary concern&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lasso takes a security-first approach with reputation scoring for MCP servers, real-time threat detection, and PII leakage prevention via Presidio integration. If your primary concern is preventing prompt injection and protecting sensitive data flowing through agent-tool interactions, Lasso addresses that more directly than most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Genuine limitations:&lt;/strong&gt; The feature set is narrower for general MCP management. Routing, observability, and governance capabilities are less mature than the enterprise-focused options. It is best thought of as a security layer to add on top of other infrastructure, not a complete gateway solution on its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCP Gateway&lt;/th&gt;
&lt;th&gt;RBAC Depth&lt;/th&gt;
&lt;th&gt;Audit Logging&lt;/th&gt;
&lt;th&gt;SOC 2 Certified&lt;/th&gt;
&lt;th&gt;Self-Hosted&lt;/th&gt;
&lt;th&gt;Also Routes LLMs?&lt;/th&gt;
&lt;th&gt;Pre-Built Integrations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TrueFoundry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep, per-tool&lt;/td&gt;
&lt;td&gt;Yes, full traces&lt;/td&gt;
&lt;td&gt;Yes (also HIPAA, ITAR)&lt;/td&gt;
&lt;td&gt;Yes (VPC/on-prem)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (deploy your own)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MintMCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Role and tool-level&lt;/td&gt;
&lt;td&gt;Yes, SOC 2 format&lt;/td&gt;
&lt;td&gt;Yes (Type II)&lt;/td&gt;
&lt;td&gt;No (managed only)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Composio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Action-level RBAC&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (SOC 2 + ISO 27001)&lt;/td&gt;
&lt;td&gt;No (managed only)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;850+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker MCP Gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None built-in&lt;/td&gt;
&lt;td&gt;None built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCPJungle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IBM ContextForge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No (protocol bridging)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lasso Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Yes (security focus)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What I Learned From the Actual Evaluation
&lt;/h2&gt;

&lt;p&gt;The thing that surprised me most was how few teams have thought through the full lifecycle question. Most conversations start with "how do I secure access to MCP servers?" and stop there. But the harder question is "who owns deploying those servers, updating them, monitoring their health, and deprecating them when the underlying API changes?"&lt;/p&gt;

&lt;p&gt;TrueFoundry is the only platform that takes a position on the whole lifecycle rather than just the gateway layer. That is why it ended up being the recommendation for my financial services client, whose platform team needed a single pane of glass, not a collection of specialized tools stitched together.&lt;/p&gt;

&lt;p&gt;MintMCP won a secondary engagement at the same company for a faster-moving team that needed to wrap some internal STDIO servers quickly and could not wait for the full platform rollout. The one-click compliance workflow is legitimately useful if you are willing to accept managed-only deployment.&lt;/p&gt;

&lt;p&gt;Composio came up in every conversation about rapid prototyping and SaaS connectivity. The breadth of pre-built integrations is a genuine time-saver at the prototyping stage. The teams that outgrew it did so because they needed to customize connector behavior and hit the closed-source wall.&lt;/p&gt;

&lt;p&gt;Docker MCP Gateway is fine for local work. I keep seeing it in lists of "enterprise MCP gateways" and that categorization does it a disservice. It is a developer tool, not a production platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Few Questions Worth Asking Before You Pick
&lt;/h2&gt;

&lt;p&gt;If you are going through your own evaluation, these are the questions that cut through a lot of marketing noise:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Where does the data actually go?&lt;/strong&gt; Managed-only gateways require you to trust a vendor's infrastructure with your agent's tool calls, which may include internal API responses, database query results, and other sensitive payloads. If your security or compliance team has data residency requirements, that eliminates several options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Do you need LLM routing and MCP governance in the same system?&lt;/strong&gt; If yes, that significantly narrows the field. TrueFoundry is the main option that does both natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. How much platform team capacity do you have?&lt;/strong&gt; More capable platforms require more operational investment. If you have a small team, a managed option with less flexibility might be the right tradeoff even if it costs more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What does your existing MCP server estate look like?&lt;/strong&gt; Mostly STDIO local servers? MintMCP's conversion workflow is genuinely compelling. Starting from scratch and need SaaS connectivity fast? Composio's 850-plus integrations is hard to beat in the short term.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Verdicts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pick TrueFoundry&lt;/strong&gt; if you need MCP governance unified with LLM routing and model deployment, are running production AI workloads at scale, and have a platform team that can take ownership of the infrastructure. The Gartner recognition and Fortune 1000 customer track record are real signals, not just marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick MintMCP&lt;/strong&gt; if you need SOC 2 compliance out of the box and zero infrastructure management, and you are comfortable with a managed-only deployment model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Composio&lt;/strong&gt; if your agents need to connect to dozens of SaaS tools immediately and you can live with the tradeoffs around governance depth and customizability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick Docker MCP Gateway&lt;/strong&gt; if you are still building locally and want container isolation. Plan to revisit this decision before you go to production.&lt;/p&gt;

&lt;p&gt;The right answer genuinely depends on where your organization is today: how mature your platform team is, what your compliance requirements look like, and whether you are still experimenting or already running agents that touch production systems. Hopefully the breakdown above gives you enough signal to figure out which of those buckets you are in.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you have run any of these in production and have a different experience than what I described, I would genuinely love to hear it in the comments. My evaluation was thorough but not exhaustive, and the landscape is moving fast enough that things I found six weeks ago may already be out of date.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>mcp</category>
    </item>
    <item>
      <title>GitHub Broke Git: The Merge Queue Bug That Silently Deleted Your Code</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Sun, 03 May 2026 03:09:35 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/github-broke-git-the-merge-queue-bug-that-silently-deleted-your-code-4f7i</link>
      <guid>https://dev.to/varshithvhegde/github-broke-git-the-merge-queue-bug-that-silently-deleted-your-code-4f7i</guid>
      <description>&lt;p&gt;If you use GitHub's merge queue and had a rough week around April 23rd, 2026, you were not imagining things. Your code actually disappeared. Not because of a bad commit, not because of a rogue team member, but because GitHub itself quietly deleted it.&lt;/p&gt;

&lt;p&gt;This is the story of what happened, why it was way worse than the official numbers suggest, and what it means for the way we all trust the tools we build on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Day GitHub Stopped Being Git
&lt;/h2&gt;

&lt;p&gt;At 16:05 UTC on April 23rd, 2026, a regression crept into GitHub's merge queue. For the next three and a half hours, engineers around the world were reviewing pull requests, clicking "merge," and watching everything look completely fine. Green checks. Clean diffs. No warnings.&lt;/p&gt;

&lt;p&gt;What was actually happening behind the scenes was quietly horrifying.&lt;/p&gt;

&lt;p&gt;A PR with a perfectly reasonable &lt;code&gt;+29 / -34&lt;/code&gt; diff would get approved and queued. What landed on &lt;code&gt;main&lt;/code&gt; was a commit worth &lt;code&gt;+245 / -1,137&lt;/code&gt;. Thousands of lines of code that other engineers had already shipped, reviewed, and moved on from, just gone. And every merge that came after went in on top of that broken history.&lt;/p&gt;

&lt;p&gt;The UI showed zero problems. The status page showed no outage. The platform was lying to everyone's faces.&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%2F4z56m261nmof6a1ed34z.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%2F4z56m261nmof6a1ed34z.png" alt="Git commit graph showing incorrect merge base" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Actually Went Wrong Under the Hood
&lt;/h2&gt;

&lt;p&gt;GitHub's merge queue works by creating a temporary branch for each PR in the queue. Normally, that temp branch starts from the tip of &lt;code&gt;main&lt;/code&gt; plus the PR's diff. CI runs against it, it passes, it lands.&lt;/p&gt;

&lt;p&gt;On April 23rd, the queue started building those temp branches from the wrong starting point. Instead of branching from the current tip of &lt;code&gt;main&lt;/code&gt;, it was branching from wherever the feature branch had originally diverged from main, potentially dozens or hundreds of commits back.&lt;/p&gt;

&lt;p&gt;Then it pushed the entire contents of that temp branch to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So if your feature branch was 50 commits behind main when it hit the queue, the "merge" silently removed those 50 commits of other people's work as a side effect of landing yours. CI passed because the temp branch on its own was internally consistent. &lt;code&gt;main&lt;/code&gt; blew up because the temp branch had nothing to do with the current state of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The root cause? A new code path that adjusted merge base computation was meant to be gated behind a feature flag for an unreleased feature. The gating was incomplete. The new behavior leaked into production and applied to all squash merge groups.&lt;/p&gt;

&lt;p&gt;Three things made this bug particularly nasty:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The PR UI lied.&lt;/strong&gt; You reviewed &lt;code&gt;+29/-34&lt;/code&gt;. The commit that landed was &lt;code&gt;+245/-1,137&lt;/code&gt;. The thing engineers approved was not the thing that merged. That breaks the most fundamental contract of a code review system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It was completely silent.&lt;/strong&gt; No merge conflict. No failed check. No banner on the PR. Teams only found out when someone noticed code on &lt;code&gt;main&lt;/code&gt; that should have been there simply was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It scaled with repo activity.&lt;/strong&gt; The faster a repo was merging, the further feature branches had drifted from &lt;code&gt;main&lt;/code&gt;, and the more damage each bad merge did. The teams that relied most on merge queue got hit the hardest.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Human Cost
&lt;/h2&gt;

&lt;p&gt;This was not a theoretical problem. Engineering teams spent entire afternoons in incident mode: combing through commit graphs, reconstructing deleted code by hand, coordinating recovery across multiple repos, and filing support tickets that would take days to hear back on.&lt;/p&gt;

&lt;p&gt;One organization reported that every single team running on GitHub's merge queue got hit, with dozens of bad commits each and hundreds of existing commits clobbered before anyone noticed. One company alone claimed to have experienced over 200 ruined PRs.&lt;/p&gt;

&lt;p&gt;GitHub later said 2,092 pull requests across 230 repositories were affected during the impact window of April 22 to 23. Earlier messaging from GitHub's COO on X had put the number at 2,804 PRs, and some community members pushed back hard on both figures given what individual companies were experiencing.&lt;/p&gt;

&lt;p&gt;The incident was not detected by GitHub's automated monitoring because it affected merge commit correctness rather than availability. GitHub only became aware of the regression at 19:38 UTC, following an increase in customer support inquiries. The fix, a revert and force-deploy, was complete by 20:43 UTC. Three hours and thirty-three minutes of silent corruption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Status Page Was Useless
&lt;/h2&gt;

&lt;p&gt;Here is the part that stings. If you checked GitHub's status page on April 23rd, you probably saw nothing alarming. There was no major outage reported. No partial outage.&lt;/p&gt;

&lt;p&gt;That is because GitHub's status page calculus specifically excludes "Degraded Performance" from downtime numbers. The platform itself never went down. Developers could still push code, open PRs, and click merge. The fact that clicking merge was silently destroying their codebase did not register as an incident on the dashboard.&lt;/p&gt;

&lt;p&gt;This is a telling gap. Uptime and correctness are not the same thing. A bank that processes your transactions but records them incorrectly is not "up." GitHub processed the merges. It just produced wrong results. The status page was not built to catch that kind of failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Was Not an Isolated Bad Day
&lt;/h2&gt;

&lt;p&gt;It would be easier to move on from this if it were a one-off. But April 2026 was a genuinely rough stretch for GitHub.&lt;/p&gt;

&lt;p&gt;Four days after the merge queue incident, on April 27th, GitHub's Elasticsearch cluster became overloaded, likely from a botnet attack, and search-backed UI surfaces stopped returning results. Pull request lists went blank. Issues disappeared from view. Projects and Actions workflow pages showed nothing. The underlying data was still there, but developers could not see it.&lt;/p&gt;

&lt;p&gt;And then, on April 28th, the same morning GitHub's CTO published an apology post about reliability, a separate security disclosure dropped: researchers at Wiz had found a critical remote code execution vulnerability in GitHub's &lt;code&gt;git push&lt;/code&gt; pipeline (CVE-2026-3854, CVSS 8.7). A single crafted &lt;code&gt;git push&lt;/code&gt; with injected options could reach unsandboxed code execution on GitHub's servers. It was patched in 75 minutes on github.com, but the timing was brutal.&lt;/p&gt;

&lt;p&gt;Three significant failures in five days. Merge queue correctness. Search collapse. An RCE in the core git push path.&lt;/p&gt;

&lt;p&gt;GitHub's CTO, Vlad Fedorov, acknowledged in the April 28th post that none of this is acceptable. He also revealed the scale of what GitHub is dealing with: the company had planned to scale capacity by 10x in October 2025. By February 2026, projections driven by agentic development workflows (AI coding tools like Copilot, Cursor, and Codex flooding the platform with automated PRs) forced a rethink to a 30x redesign. GitHub is now hitting peaks of 90 million merged PRs and 1.4 billion commits.&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%2Fimages.unsplash.com%2Fphoto-1555099962-4199c345e5dd%3Fw%3D1200%26q%3D80" 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%2Fimages.unsplash.com%2Fphoto-1555099962-4199c345e5dd%3Fw%3D1200%26q%3D80" alt="Developer incident response" width="1200" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Deeper Architectural Problem
&lt;/h2&gt;

&lt;p&gt;There is a reason this specific failure mode existed. GitHub's merge queue constructs merge commits through a code path that is separate from how a regular PR merge works. Two code paths, two places where behavior can quietly diverge.&lt;/p&gt;

&lt;p&gt;This is the danger that comes with delegation. A merge queue is supposed to automate exactly what a human would do when clicking "Merge pull request." The moment it does something a human would not do, because it has its own logic for building the merge commit, it can silently produce commits nobody wrote and nobody approved.&lt;/p&gt;

&lt;p&gt;This is not just a GitHub problem. It is a pattern that shows up every time we give automated systems write access to things that matter. Queues, bots, AI agents. As long as those systems are doing something equivalent to what a human would do, the failure modes are familiar. When they start doing things a human would not do, the failures become invisible until the damage is already done.&lt;/p&gt;

&lt;p&gt;The lesson is not to avoid merge queues. It is to make sure that whatever writes to &lt;code&gt;main&lt;/code&gt; stays as close as possible to boring, well-understood git operations, with no novel logic in the merge commit path that reviewers cannot audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Will Anyone Actually Leave?
&lt;/h2&gt;

&lt;p&gt;After something like this, the obvious question is whether developers will migrate off GitHub. And the honest answer is: probably not in any significant numbers.&lt;/p&gt;

&lt;p&gt;GitHub is deeply embedded. CI pipelines, webhook integrations, RBAC policies, Actions workflows, third-party app permissions, team structures, pull request history. Migration is not just switching a remote URL. It is months of work and coordination.&lt;/p&gt;

&lt;p&gt;That stickiness is real and it is not purely irrational. GitHub is still where most open source lives. It is still where most integrations point. It is still the default. The addictive hold it has over the development ecosystem is less like a premium SaaS product and more like a utility. You do not switch utilities because of a bad week.&lt;/p&gt;

&lt;p&gt;But what this incident should change is the baseline of trust. GitHub is infrastructure. And infrastructure that silently corrupts your data, even for a few hours, with no visible error, is infrastructure you need to have a recovery plan for.&lt;/p&gt;

&lt;p&gt;The minimum response is not migration. It is verification. Audit squash merges in merge queue groups of two or more PRs from the April 22 to 23 window. Write down which parts of your build and deploy pipeline silently assume git history is correct. Then make that assumption visible somewhere it can be challenged.&lt;/p&gt;




&lt;h2&gt;
  
  
  What GitHub Says It Is Doing About It
&lt;/h2&gt;

&lt;p&gt;GitHub's post-incident response included a few concrete commitments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expanding test coverage for merge correctness validation&lt;/li&gt;
&lt;li&gt;Adding regression checks that validate resulting git contents across supported merge configurations before reaching production&lt;/li&gt;
&lt;li&gt;Migrating performance-sensitive code from its older Ruby codebase to Go&lt;/li&gt;
&lt;li&gt;Moving systems to public cloud infrastructure to handle the 30x scale requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The April 23rd bug specifically was caused by incomplete feature flagging on a new code path. The fix was a revert. The longer-term fix is better test coverage for multi-PR merge queue groups, which were apparently underrepresented in existing test suites.&lt;/p&gt;




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

&lt;p&gt;GitHub's merge queue, for a few hours on April 23rd, 2026, broke the most fundamental contract of version control: that what you approve is what merges. It did it silently, with clean green UI, no errors, and no status page entry.&lt;/p&gt;

&lt;p&gt;The code was still there in Git object storage. But the branch history was wrong, and no automated system could safely repair it across every affected repository. Engineers had to do it by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  That is the thing that lingers. Git is supposed to be the boring, reliable layer that everything else is built on. When the boring layer gets interesting, it gets interesting in the worst possible way.
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;If you found this useful, drop a comment below or follow for more deep dives into the tools we trust (sometimes too much).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>git</category>
      <category>github</category>
      <category>programming</category>
    </item>
    <item>
      <title>7 AI Gateways That Actually Work in Production (2026 Guide)</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Wed, 29 Apr 2026 12:19:13 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/7-ai-gateways-that-actually-work-in-production-2026-guide-2p4d</link>
      <guid>https://dev.to/varshithvhegde/7-ai-gateways-that-actually-work-in-production-2026-guide-2p4d</guid>
      <description>&lt;p&gt;Let me start with an admission. I resisted using an AI gateway for longer than I should have.&lt;/p&gt;

&lt;p&gt;My reasoning was the kind engineers convince themselves is pragmatic. "I'll just call the APIs directly, it's faster to ship, I'll add abstraction later." And for a while, it worked. Until the night an Anthropic outage knocked my app offline for two hours. Until the morning a recursive agent loop racked up thousands of dollars in charges before anyone woke up. Until the security audit flagged raw API keys scattered across four different repos.&lt;/p&gt;

&lt;p&gt;At that point, "later" arrived.&lt;/p&gt;

&lt;p&gt;I've spent the past several months evaluating AI gateways seriously. Not as a researcher, but as someone trying to put them in front of real production workloads. This is what I found.&lt;/p&gt;




&lt;h2&gt;
  
  
  First: What Does an AI Gateway Actually Do?
&lt;/h2&gt;

&lt;p&gt;Before the list, let me be specific about what we're talking about, because the category name is increasingly used to mean very different things.&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%2Fh8pw99qdxzzwwkwoep1u.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%2Fh8pw99qdxzzwwkwoep1u.png" alt="LLM API gateway architecture diagram" width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gartner defines an AI gateway as "a technology or platform that acts as an intermediary between applications and various AI services or models." That is the clean academic definition. In practice, a good AI gateway is the layer that keeps your AI app running when things break. And things always break.&lt;/p&gt;

&lt;p&gt;Concretely, that means handling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt; - intelligently directing requests to the right model based on cost, latency, or availability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failover&lt;/strong&gt; - automatically switching providers when one goes down, often in under 50ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost controls&lt;/strong&gt; - per-team or per-key budget limits so no single runaway agent bankrupts you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key management&lt;/strong&gt; - one secure central store for credentials instead of env vars scattered across repos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability&lt;/strong&gt; - request-level traces, latency metrics, and token usage across every provider in a single dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt; - audit logs, role-based access control, and data residency guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different gateways prioritize different things. Some are razor-thin proxies optimized for speed. Others are full control planes designed to govern how an entire organization uses AI. The right choice depends entirely on where your pain is.&lt;/p&gt;

&lt;p&gt;Here are the seven worth knowing in 2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gateway&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;MCP Support&lt;/th&gt;
&lt;th&gt;On-Prem/VPC&lt;/th&gt;
&lt;th&gt;Compliance&lt;/th&gt;
&lt;th&gt;Gartner Recognized&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TrueFoundry&lt;/td&gt;
&lt;td&gt;~3-4ms&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;VPC, On-Prem, Air-Gapped&lt;/td&gt;
&lt;td&gt;SOC 2, HIPAA, ITAR&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Enterprise with compliance + deployment needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helicone&lt;/td&gt;
&lt;td&gt;under 5ms P95&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Self-hosted option&lt;/td&gt;
&lt;td&gt;SOC 2&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Observability-first teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;~15ms&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Managed only&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Prototyping, widest model access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requesty&lt;/td&gt;
&lt;td&gt;~8ms P50&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;GDPR (EU endpoint)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Fast multi-model routing with analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Singulr AI&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;In progress&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;AI governance-focused orgs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inworld Router&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Quality-weighted routing experiments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Braintrust Gateway&lt;/td&gt;
&lt;td&gt;Cached under 100ms&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise tier only&lt;/td&gt;
&lt;td&gt;SOC 2&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Eval + routing in one workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  1. TrueFoundry AI Gateway
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Enterprise Production Pick
&lt;/h3&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%2Fspugnj4tfrc14bdl6xt0.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%2Fspugnj4tfrc14bdl6xt0.png" alt="TrueFoundry AI Gateway enterprise platform" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll be honest. TrueFoundry was not the first gateway I tried. It kept coming up in conversations with platform engineers at companies doing serious AI at scale, and once I actually dug in, the reason became clear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.truefoundry.com/ai-gateway" rel="noopener noreferrer"&gt;TrueFoundry is an enterprise AI gateway&lt;/a&gt; and more specifically, it is the only &lt;a href="https://www.businesswire.com/news/home/20260220396246/en/CORRECTING-and-REPLACING-TrueFoundry-Recognized-as-a-Representative-Vendor-in-Gartner-Market-Guide-for-AI-Gateways" rel="noopener noreferrer"&gt;Gartner-recognized AI gateway&lt;/a&gt; that also handles model deployment and GPU orchestration in the same platform. Most gateways on this list are proxies with dashboards. TrueFoundry is closer to a full AI control plane, the kind of thing a platform team would build internally at a large company, except you do not have to build it yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The numbers that matter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The platform handles over &lt;strong&gt;10 billion requests per month&lt;/strong&gt; for Fortune 1000 customers including NVIDIA and Siemens Healthineers. The gateway adds roughly 3-4ms latency overhead per request and can sustain 350+ RPS on a single vCPU. These are not lab benchmarks. They are the numbers that show up in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it genuinely stands apart on compliance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SOC 2, HIPAA, and ITAR certified. For anyone in healthcare, financial services, defense, or any regulated industry, this is often the conversation that ends competitor evaluations. Most other gateways on this list have none of these certifications, or are still working toward them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The deployment flexibility is real&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VPC, on-premises, and air-gapped deployments are all supported. If your security posture means data cannot touch a public cloud, TrueFoundry actually works. Not as an afterthought, but as a first-class deployment mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MCP piece deserves its own moment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As AI agents multiply, teams are suddenly managing not just LLM calls but tool access: MCP servers for code execution, database queries, web search, enterprise integrations. TrueFoundry unifies LLM routing and MCP governance in the same control plane, with OAuth2, RBAC, and audit logging applied to every tool call. You can &lt;a href="https://www.truefoundry.com/ai-gateway" rel="noopener noreferrer"&gt;register internal MCP servers&lt;/a&gt;, define who can access what, and monitor agent tool usage alongside your LLM traffic, all in one place. No other gateway on this list does that.&lt;/p&gt;

&lt;p&gt;On Gartner Peer Insights, one enterprise customer said: "AI Gateway is a single pane where I can see all the models, their associated cost, track requests... it provides an easy way to integrate with MCP servers which does a very heavy lift." That lines up with what I have heard from teams using it at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it genuinely falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TrueFoundry is a heavier platform. If your requirement is "I need a quick proxy to route between GPT-4 and Claude," this is more infrastructure than you need. It is also strongest when there is a dedicated platform or infra team who can own it. Solo developers or very small teams will find the setup investment harder to justify compared to lighter alternatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bottom line&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;TrueFoundry is the only Gartner-recognized AI gateway on this list and the only one that unifies LLM routing, MCP governance, and model deployment in a single control plane. If you are running production AI for an enterprise with compliance requirements, it is in a different category from the proxies below.&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://www.truefoundry.com/ai-gateway" rel="noopener noreferrer"&gt;truefoundry.com/ai-gateway&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Helicone AI Gateway
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Observability-First Pick
&lt;/h3&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%2Fgs0d1phpum5zihjxzz8u.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%2Fgs0d1phpum5zihjxzz8u.png" alt="Helicone LLM observability and analytics dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Helicone has earned genuine respect in the developer community for a specific reason. If you want to understand what your AI application is actually doing, it is excellent.&lt;/p&gt;

&lt;p&gt;It is Rust-based, open-source, and fast. The team describes it as "the NGINX of LLMs," and that is not just marketing. The architecture reflects it. You get a unified API for 100+ providers through a single OpenAI-compatible endpoint, with automatic failover, load balancing, and per-request logging built in from the start.&lt;/p&gt;

&lt;p&gt;The analytics dashboard is one of the more useful ones I have seen: per-request cost tracking, model comparison, session-level traces, and usage patterns broken out by team, model, or environment. For understanding where your AI spend is actually going, Helicone is hard to beat.&lt;/p&gt;

&lt;p&gt;It is also SOC 2 certified and GDPR compliant, with a self-hosting option for teams that need infrastructure control. That is a meaningful step up from pure managed-only options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No MCP gateway support. If you are building agents that need governed tool access, you will need to look elsewhere for that layer. Governance features like RBAC depth and policy enforcement are more basic compared to enterprise platforms. It is primarily an observability platform with routing layered on, not a full deployment and governance story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; teams where LLM observability and cost analytics are the primary pain point. If you already have routing handled but want real visibility into what is happening across your models, Helicone is a solid, developer-friendly choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. OpenRouter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Widest Model Access, Fastest to Start
&lt;/h3&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%2Fzkk7s8kt4d3spl12u2qf.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%2Fzkk7s8kt4d3spl12u2qf.png" alt="OpenRouter unified AI model API interface" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenRouter is how I reach 300+ models through one API when I am prototyping. No infrastructure to manage, unified billing across providers, and instant access to everything from GPT-5 to Llama to Mistral variants through a single OpenAI-compatible endpoint.&lt;/p&gt;

&lt;p&gt;The pricing model is worth understanding correctly. OpenRouter actually passes through provider pricing at or near cost. It is a 5.5% platform fee on credit purchases, not a per-token markup on inference. For most use cases, you are paying what you would pay the provider directly, plus a small convenience fee for the unified access. They do not train on your data, and there is a growing free tier with 25+ zero-cost models for getting started.&lt;/p&gt;

&lt;p&gt;For prototyping, experimenting with different models, or any project where you need breadth over depth, OpenRouter is genuinely hard to beat on speed of getting started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managed only, no self-hosting option. No MCP support. Governance features are minimal: no RBAC, no compliance certifications, no fine-grained access controls built for regulated industries. The 100 API calls per 60 seconds default throttling can become a real constraint for high-volume agent pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; prototyping, side projects, or teams that need fast access to the widest range of models and are not yet in a compliance conversation.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Requesty
&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%2Fsbwraegix52f5c8o2akm.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%2Fsbwraegix52f5c8o2akm.png" alt="Requesty AI Gateway" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Smarter Than It Looks
&lt;/h3&gt;

&lt;p&gt;Requesty is a gateway I underestimated at first glance. The website looks simple. That turned out to be a mistake.&lt;/p&gt;

&lt;p&gt;Requesty is a unified LLM gateway for 400+ models, and what sets it apart from pure model-access tools is the routing intelligence. It includes smart routing that analyzes request type and auto-selects the cheapest viable model, cross-provider semantic caching (which can cut token costs by up to 80% on repeated queries), real-time PII redaction, and sub-50ms automatic failover when a provider goes down.&lt;/p&gt;

&lt;p&gt;According to their own data, 70,000+ developers use it and it processes 90+ billion tokens daily. Those are numbers that suggest it is more battle-tested than its marketing implies. There is an EU endpoint for GDPR compliance, per-key spending limits, and a genuinely useful analytics dashboard.&lt;/p&gt;

&lt;p&gt;Setup is three lines of code. Swap the base URL. Done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://router.requesty.ai/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-requesty-key&lt;/span&gt;&lt;span class="sh"&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;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Managed only, no self-hosting or VPC deployment. No MCP governance. No enterprise compliance certifications beyond GDPR. For teams in regulated industries or those needing air-gapped deployment, it does not get you there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; developers who want a capable, managed multi-model gateway with smart routing and cost optimization, without the infrastructure overhead of a full enterprise platform.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Singulr AI
&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%2Fckka6m94osvbkfjz5vaq.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%2Fckka6m94osvbkfjz5vaq.png" alt="Singulr AI Gateway" width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Governance-Focused Newcomer
&lt;/h3&gt;

&lt;p&gt;Singulr AI is an enterprise AI governance platform backed by Nexus Venture Partners and Dell Technologies Capital. It raised $10M in early 2025 with a specific focus: helping security, IT, privacy, and compliance teams gain visibility and control over how AI is being used across an organization.&lt;/p&gt;

&lt;p&gt;The approach is distinctive. It includes a continuously updated AI risk intelligence system that profiles models and agents, classifies them in real time, and recommends safer alternatives. It also offers application-aware red teaming that simulates real-world threats before deployment.&lt;/p&gt;

&lt;p&gt;For CISOs and compliance teams, this is interesting. It is a governance-first angle that most gateway vendors leave to someone else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is a newer entrant with limited public production track record at Fortune 1000 scale. The feature set is narrower than full gateway platforms. It is primarily governance and security, not a complete routing, failover, and deployment story. Pricing is not public.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; organizations where AI governance, risk scoring, and compliance team enablement are the primary requirements, and who are comfortable evaluating a platform that is still building its enterprise reference base.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Inworld Router
&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%2Fqk2vfuccbayd3w1ip1bl.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%2Fqk2vfuccbayd3w1ip1bl.png" alt="Inworld Router AI Gateway" width="799" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  An Interesting Idea Worth Watching
&lt;/h3&gt;

&lt;p&gt;Inworld Router takes a genuinely different approach to the routing problem. Instead of routing based purely on cost or availability, it routes on business-level metrics: cost per output quality, task complexity, latency targets. The idea is that not every request needs the smartest and most expensive model, and a router that understands the nature of a request can make smarter tradeoffs than one that just round-robins.&lt;/p&gt;

&lt;p&gt;That is a legitimate insight, and as a concept it points toward where sophisticated AI infrastructure is heading.&lt;/p&gt;

&lt;p&gt;In practice today, it is primarily built for Inworld's own gaming and character AI use case. The ecosystem is small, community support is limited, and it is not a general-purpose enterprise gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; teams in gaming or character AI who want to experiment with quality-weighted routing. Worth keeping an eye on as the concept matures.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Braintrust Gateway
&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%2Fl16lkuzvdxdd2g4a6jy4.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%2Fl16lkuzvdxdd2g4a6jy4.png" alt="Braintrust Gateway" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Eval-First Option
&lt;/h3&gt;

&lt;p&gt;Braintrust is fundamentally an evaluation and observability platform that also includes a capable gateway. The integration between the two is the real story. Requests that flow through the gateway automatically feed into Braintrust's tracing and evaluation pipeline. You can run evaluations against production traffic, compare model performance across experiments, and catch regressions in CI/CD before they reach users.&lt;/p&gt;

&lt;p&gt;The gateway supports 100+ models including GPT-5, Claude 4, and Gemini 2.5. Caching is encrypted per-API-key using AES-GCM, with sub-100ms response times for cached requests. There is a generous free tier (1M trace spans, 10k evaluation scores) and SOC 2 Type II certification on the enterprise side.&lt;/p&gt;

&lt;p&gt;One important note: their original AI proxy is now deprecated. They have migrated to a full gateway product, which is a meaningful upgrade for production reliability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gateway features are secondary to the eval platform, which is by design, but means it is not a full story for failover, MCP governance, or compliance-heavy deployments. Self-hosting is only available on the enterprise tier. At $249/month for the Pro plan, it is not the lightest option for teams that only need routing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for&lt;/strong&gt; engineering teams doing active prompt optimization and model comparison who want routing and evaluation tightly integrated, and do not want to stitch together separate tools for each.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Actually Choose
&lt;/h2&gt;

&lt;p&gt;After spending real time with all of these, here is my honest decision framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The compliance conversation is the first filter.&lt;/strong&gt; If your security team needs SOC 2, HIPAA, or ITAR, or if data cannot leave your cloud, the list immediately narrows to one serious option: TrueFoundry. This is not a sales pitch. It is just where the certifications are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The MCP question is the second filter.&lt;/strong&gt; If you are building agents that need governed tool access, only TrueFoundry covers this layer natively today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you clear both of those, the rest is about fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick &lt;strong&gt;&lt;a href="https://www.truefoundry.com/ai-gateway" rel="noopener noreferrer"&gt;TrueFoundry&lt;/a&gt;&lt;/strong&gt; if you need enterprise governance, compliance, and model deployment in one platform&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Helicone&lt;/strong&gt; if observability and cost analytics are your primary pain and you want something developer-friendly and open-source&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;OpenRouter&lt;/strong&gt; if you are prototyping and want the fastest possible access to the widest range of models&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Requesty&lt;/strong&gt; if you want a capable managed gateway with smart routing and you are not in a compliance-heavy environment&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;Braintrust&lt;/strong&gt; if prompt evaluation and model quality monitoring are central to your workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where This Category Is Going
&lt;/h2&gt;

&lt;p&gt;Something I have noticed in 2026 is that the definition of "AI gateway" keeps expanding. A year ago it meant a proxy with routing logic. Now teams are asking their gateway to handle agent tool access via MCP, govern agent-to-agent communication, manage model deployment, and provide compliance audit trails across all of 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%2Feevb9oq9vvwlvbfb7oph.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%2Feevb9oq9vvwlvbfb7oph.png" alt="MCP gateway agent tool orchestration architecture" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a lot to ask of a single layer. Most of the lighter options on this list handle one or two of these well. TrueFoundry is the only one I have seen genuinely attempting the full stack, and it has the production evidence to back that up: 10B+ requests per month, Fortune 1000 customers, and Gartner recognition.&lt;/p&gt;

&lt;p&gt;Whether you want one vendor for all of that, or best-of-breed at each layer, is a real architectural choice. Either can work. The important thing is making it deliberately, rather than discovering two years in that your "lightweight proxy" cannot support what your AI stack has become.&lt;/p&gt;




&lt;p&gt;What is your experience been? I am especially curious if anyone has moved from a lighter gateway to something heavier, or the other direction, and what triggered that switch. Drop a comment below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>programming</category>
      <category>llm</category>
    </item>
    <item>
      <title>I Spent 3 Days Debugging Our LLM Setup. Turns Out We Needed an AI Gateway the Whole Time.</title>
      <dc:creator>Varshith V Hegde</dc:creator>
      <pubDate>Wed, 15 Apr 2026 08:46:08 +0000</pubDate>
      <link>https://dev.to/varshithvhegde/i-spent-3-days-debugging-our-llm-setup-turns-out-we-needed-an-ai-gateway-the-whole-time-50a2</link>
      <guid>https://dev.to/varshithvhegde/i-spent-3-days-debugging-our-llm-setup-turns-out-we-needed-an-ai-gateway-the-whole-time-50a2</guid>
      <description>&lt;p&gt;Let me tell you about a Friday afternoon I'd rather forget.&lt;/p&gt;

&lt;p&gt;Three teams, four models, six API keys living in different &lt;code&gt;.env&lt;/code&gt; files, one very angry compliance officer, and me just staring at a terminal trying to figure out why we got a $1,400 OpenAI bill for a feature that was supposed to cost fifty bucks.&lt;/p&gt;

&lt;p&gt;That was my "okay something is genuinely broken here" moment.&lt;/p&gt;

&lt;p&gt;Not some big insight. Just a $1,400 invoice and dead silence on a Slack thread for about ten minutes.&lt;/p&gt;

&lt;p&gt;If you've felt even a small version of that, this post is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  So what actually is an AI Gateway?
&lt;/h2&gt;

&lt;p&gt;Not the textbook answer. That one goes something like "middleware that abstracts your LLM provider calls." Technically fine, tells you nothing.&lt;/p&gt;

&lt;p&gt;Here's how I actually think about it.&lt;/p&gt;

&lt;p&gt;You know how bigger engineering orgs eventually build out a platform team? Before that team exists, every squad is doing their own thing. Their own CI setup, their own infra configs, their own credentials. It mostly works. Until it doesn't. And then it catastrophically doesn't all at once.&lt;/p&gt;

&lt;p&gt;An AI Gateway is basically that platform layer, except it's for LLMs.&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%2Fkjuor74bks4xvbsncv14.webp" 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%2Fkjuor74bks4xvbsncv14.webp" alt="AI Gateway" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every single request your app makes to any model (OpenAI, Anthropic, a self-hosted Llama, whatever you're running) goes through it. Because everything flows through one place, you finally get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One set of credentials instead of keys scattered across five repos&lt;/li&gt;
&lt;li&gt;Rate limits and budgets that are actually enforced&lt;/li&gt;
&lt;li&gt;Cost tracking per team, per model, per request&lt;/li&gt;
&lt;li&gt;Guardrails that catch PII before it leaves your infra&lt;/li&gt;
&lt;li&gt;One place to look when something blows up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One control plane. Every team. Every model.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture is simpler than it sounds
&lt;/h2&gt;

&lt;p&gt;Here's what happens when you put a gateway in the middle:&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%2Fhjgbzpismfi3ilj62wmy.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%2Fhjgbzpismfi3ilj62wmy.png" alt="Excalidraw AI gateway" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Request comes in from your app, gateway catches it, validates auth, checks rate limits, applies input guardrails, picks the right provider, logs everything, checks the response output, sends it back. That's the whole flow.&lt;/p&gt;

&lt;p&gt;Your application code doesn't change. You stop pointing at &lt;code&gt;api.openai.com&lt;/code&gt; directly and point at your gateway instead. That's literally it from your team's perspective.&lt;/p&gt;

&lt;p&gt;The control layer just sits there doing its job quietly.&lt;/p&gt;




&lt;h2&gt;
  
  
  "But I already have an API gateway. Isn't that enough?"
&lt;/h2&gt;

&lt;p&gt;This is where most people get confused. Including me when I first looked into this.&lt;/p&gt;

&lt;p&gt;Quick answer: no. Here's why.&lt;/p&gt;

&lt;p&gt;Your API gateway (Kong, AWS API Gateway, Nginx, take your pick) understands traffic. It knows Team A sent 10,000 HTTP requests. It can enforce rate limits, handle auth tokens. That's useful.&lt;/p&gt;

&lt;p&gt;Your AI gateway understands what's actually inside those requests. It knows Team A sent &lt;strong&gt;4.2 million tokens to GPT-4o&lt;/strong&gt;, it cost &lt;strong&gt;$84&lt;/strong&gt;, average latency was &lt;strong&gt;340ms&lt;/strong&gt;, and &lt;strong&gt;3 of those requests triggered the PII guardrail&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One sees requests. The other sees meaning. That's not a small difference.&lt;/p&gt;

&lt;p&gt;For stateless REST APIs, a regular API gateway is totally fine. For LLM workloads where tokens equal money and every prompt is a potential compliance issue, you need something that actually speaks the language.&lt;/p&gt;




&lt;h2&gt;
  
  
  Do you actually need one right now though?
&lt;/h2&gt;

&lt;p&gt;Let me skip the usual "it depends" and be direct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're probably fine without one if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One team, one model, one use case&lt;/li&gt;
&lt;li&gt;Nobody is asking about costs yet&lt;/li&gt;
&lt;li&gt;Zero compliance requirements&lt;/li&gt;
&lt;li&gt;It's a POC or side project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't add infrastructure you don't need. Raw SDK calls are fast to ship. Keep it simple when simple works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You've outgrown the simple setup if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple teams are calling models independently with no visibility into what they're doing&lt;/li&gt;
&lt;li&gt;Swapping providers requires actual code changes&lt;/li&gt;
&lt;li&gt;Someone from legal or security or finance asked a question you couldn't answer&lt;/li&gt;
&lt;li&gt;You've had an API key accidentally committed to a public repo (or almost did)&lt;/li&gt;
&lt;li&gt;You can't answer "what did we spend on AI last month, by team?" without going on a scavenger hunt through billing dashboards&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is genuinely the biggest tell. If someone asks that question and you have to go digging, you already needed this.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually pushes teams over the edge
&lt;/h3&gt;

&lt;p&gt;It's never one thing. It's always a pile of smaller things that suddenly feel heavy together.&lt;/p&gt;

&lt;p&gt;DevOps realizes they can't track spend because keys are everywhere. Someone commits a key to a public repo. A team uses GPT-4 Turbo for tasks that GPT-4 Mini handles just fine, and you find out after they've burned $2K. Compliance asks for an audit trail and you have nothing.&lt;/p&gt;

&lt;p&gt;Each of those individually, fine, you deal with it. All of them stacking up at the same time? That's when the "simple" setup reveals it was never actually simple. You were just deferring the complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a production gateway actually looks like
&lt;/h2&gt;

&lt;p&gt;Okay enough talking around it. Here's what it gives you in practice, using TrueFoundry as the concrete example.&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%2Fe7wqj5il9c2jcll6z8a9.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%2Fe7wqj5il9c2jcll6z8a9.png" alt="TrueFoundry MainPage" width="799" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One API key across all providers&lt;/strong&gt;&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%2Fpzba6qd46w4cqyrq3nse.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%2Fpzba6qd46w4cqyrq3nse.png" alt="Model Unify" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your teams stop touching raw OpenAI or Anthropic keys entirely. One key, routed through the gateway, with access to every approved model. Rotate it in one place. Done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-team budgets with real enforcement&lt;/strong&gt;&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%2Fh4l0e4t4h7tccj3rwm7r.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%2Fh4l0e4t4h7tccj3rwm7r.png" alt="team" width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not "we log it and send you a Slack alert." Actual hard limits. Team hits their monthly budget, the next request gets rejected with a clear error. No surprise bills, no awkward retros about where the spend went.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic failover&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenAI goes down. It happens. Your app doesn't go down with it because requests automatically route to Anthropic or your self-hosted model. No code changes. No one gets paged. It just keeps working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full request tracing&lt;/strong&gt;&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%2Fhlrnranv1rkxfvv22jyt.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%2Fhlrnranv1rkxfvv22jyt.png" alt="request tracing" width="799" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every prompt, every response, every token count, every cost attribution. Logged and queryable. Pull a request from six months ago and reconstruct exactly what happened. This feature alone has saved me more debugging time than I can measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails that actually run everywhere&lt;/strong&gt;&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%2F42qwsft6nxn4x574gv87.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%2F42qwsft6nxn4x574gv87.png" alt="Guardrails" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PII filtering, prompt injection detection, custom output policies. You define the rule once and it applies across every team and every model. No per-team implementation, no "oops we forgot to add the check in this service."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runs inside your own environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VPC, on-prem, air-gapped. Data doesn't leave your infra. SOC 2, HIPAA, GDPR compliant. If your compliance team has ever asked "but where does the data actually go," this is finally a clean answer.&lt;/p&gt;

&lt;p&gt;Performance-wise it handles 350+ RPS on a single vCPU with sub-3ms latency so you're not adding meaningful overhead to your request path.&lt;/p&gt;

&lt;p&gt;TrueFoundry is in the 2026 Gartner Market Guide for AI Gateways and processes 10B+ requests per month for companies like Siemens Healthineers, NVIDIA, Resmed, and Automation Anywhere. Mentioning it not as a flex but as a sense of scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  The question that actually helped me decide
&lt;/h2&gt;

&lt;p&gt;Forget "do I need an AI gateway."&lt;/p&gt;

&lt;p&gt;Ask this instead: when does the cost of NOT having one start to exceed the cost of setting one up?&lt;/p&gt;

&lt;p&gt;For most teams that crossover happens way earlier than expected. For us it wasn't one event. It was the accumulation. The audit trail we didn't have. The $1,400 bill nobody could explain. The near-miss with a key in a public repo.&lt;/p&gt;

&lt;p&gt;Setting up TrueFoundry honestly took less time than the post-mortem meeting for that billing incident.&lt;/p&gt;




&lt;p&gt;Try TrueFoundry free at &lt;strong&gt;&lt;a href="https://truefoundry.com" rel="noopener noreferrer"&gt;truefoundry.com&lt;/a&gt;&lt;/strong&gt; (no credit card required, deploys on your cloud in under 10 minutes).&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What does your current setup look like? Still on raw SDK calls or have you already hit the wall? Drop a comment, genuinely curious where people are when they start asking this question.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>beginners</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
