<?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: Arya Koste</title>
    <description>The latest articles on DEV Community by Arya Koste (@arya_koste_5845807df94776).</description>
    <link>https://dev.to/arya_koste_5845807df94776</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%2F3396126%2Fff57b4f1-4d36-4ad9-8621-d0c4fd20823a.png</url>
      <title>DEV Community: Arya Koste</title>
      <link>https://dev.to/arya_koste_5845807df94776</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arya_koste_5845807df94776"/>
    <language>en</language>
    <item>
      <title>Clarity: Understanding, Out Loud</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Mon, 13 Jul 2026 04:33:01 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/clarity-understanding-out-loud-4gbe</link>
      <guid>https://dev.to/arya_koste_5845807df94776/clarity-understanding-out-loud-4gbe</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;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;Passion doesn't only live on a pitch or in a stadium chant. Sometimes it's quieter than that — it's the stubborn, protective love that makes you want to shield someone from feeling stupid or scared.&lt;/p&gt;

&lt;p&gt;Everyone has a person like this in their life: a grandparent who freezes when a dense hospital bill shows up in the mailbox. A parent whose English is a second language and who reads "final notice" and assumes the worst. A friend with a reading disability, low vision, or dyslexia who has to ask someone else to make sense of their own mail for them. Official mail is written by institutions, for institutions — dense, jargon-heavy, and never for the person actually holding the envelope, hands shaking a little, afraid to open it alone.&lt;/p&gt;

&lt;p&gt;The real problem was never that these people can't &lt;em&gt;hear&lt;/em&gt; — it's that they can't &lt;em&gt;understand&lt;/em&gt;. So &lt;strong&gt;Clarity&lt;/strong&gt; isn't a text-to-speech gadget. It's a comprehension tool first: it reads a confusing document the way a patient, well-informed friend would — breaking it down, translating the jargon, telling you what actually matters — and only then does it speak, because understanding something and having no way to absorb it is its own kind of cruelty.&lt;/p&gt;

&lt;p&gt;Clarity is built out of that same fire people usually save for rivalries and fandoms, just pointed somewhere quieter: &lt;em&gt;nobody I love should ever feel powerless in front of a piece of paper they can't make sense of.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;You hand Clarity a document — a photo of a paper letter, a PDF, or just pasted text — and it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Understands it and breaks it down&lt;/strong&gt;, not just transcribes it. A calm, structured explanation: what this document actually is, the parts that matter, exactly what you need to &lt;em&gt;do&lt;/em&gt; and by when, and anything to watch out for (fees, penalties, easy-to-miss deadlines) — turning a wall of legal or medical jargon into something a person can actually act on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adapts to how you understand best&lt;/strong&gt; — "explain it like I'm 10," plain everyday language, or full precision that preserves legal/technical detail for someone who wants it straight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaks the explanation out loud&lt;/strong&gt;, with each word highlighting in perfect sync as it's read — genuinely useful for low vision, dyslexia, or anyone who processes speech better than dense paragraphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaks it in 9+ languages&lt;/strong&gt; — the same English medical bill, understood and read back in Spanish, Hindi, Arabic, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Takes follow-up questions by voice&lt;/strong&gt; — tap the mic and ask &lt;em&gt;"how much do I actually owe, and by when?"&lt;/em&gt; — answered directly from the document, and read back to you too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turns real deadlines into calendar reminders&lt;/strong&gt; — one tap adds "pay by April 22" straight to your calendar as a &lt;code&gt;.ics&lt;/code&gt; file, but only when the document actually states a real date (it won't guess at vague phrasing like "within 30 days").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It runs as a single, no-build Node/Express server with a vanilla HTML/JS frontend — your document is processed entirely on your own machine. The only thing that ever leaves it is a few sentences of already-plain-language explanation text, sent to ElevenLabs to become audio.&lt;/p&gt;

&lt;h3&gt;
  
  
  See it in action
&lt;/h3&gt;

&lt;p&gt;I ran Clarity against three completely different kinds of confusing paperwork, live, with a real AI model doing the understanding and real ElevenLabs audio doing the speaking — no canned demo text:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document&lt;/th&gt;
&lt;th&gt;What Clarity understood and explained&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🏥 Hospital bill ($1,284.67 balance)&lt;/td&gt;
&lt;td&gt;Deductible + coinsurance breakdown, the exact due date, late-fee and credit-impact warnings, a glossary entry for "coinsurance"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧾 IRS CP2000 tax notice&lt;/td&gt;
&lt;td&gt;The $2,161.05 total owed, the April 22 payment deadline &lt;em&gt;(with a working "Add to calendar" button)&lt;/em&gt;, the 30-day response window, glossary entries for "Form 1099-NEC" and "CP2000"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚖️ Jury duty summons (explained "like I'm 10")&lt;/td&gt;
&lt;td&gt;Eligibility rules, the May 1 confirmation deadline, pay rate, and a plain-English glossary for "probation" and "felony conviction"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F8q9g5ss0pcgiyicy0efl.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%2F8q9g5ss0pcgiyicy0efl.png" alt="tax-notice" width="800" height="2584"&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%2F5fwedyszgfnwu41gp1dd.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%2F5fwedyszgfnwu41gp1dd.png" alt="jury-duty-simple" width="800" height="2285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the signature moment — the explanation read aloud with word-by-word highlighting, driven by ElevenLabs' actual audio timing, not a guess:&lt;/p&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/x8FLO75tp4s"&gt;
    &lt;/iframe&gt;
  &lt;/div&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/Aryakoste" rel="noopener noreferrer"&gt;
        Aryakoste
      &lt;/a&gt; / &lt;a href="https://github.com/Aryakoste/Clarity" rel="noopener noreferrer"&gt;
        Clarity
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Clarity 🔊&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A voice companion that reads and explains confusing letters, bills, and medical notices — in plain language, out loud.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Millions of people freeze when official mail arrives: dense legal wording, medical jargon, "final notice" red ink. That burden falls hardest on elderly people, people with low vision or dyslexia, and non-native speakers. Clarity takes a photo or PDF of that letter and gives back a calm, spoken explanation: what it is, the parts that matter, exactly what you need to do, and what to watch out for — read aloud with the words highlighting as it speaks. You can then ask follow-up questions with your voice.&lt;/p&gt;
&lt;p&gt;Built to run &lt;strong&gt;entirely on your own machine&lt;/strong&gt; — your documents never go to a third party (except the few sentences of explanation sent for text-to-speech).&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;Reads any document&lt;/strong&gt; — upload a PDF, snap a photo of paper…&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/Aryakoste/Clarity" 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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js/Express&lt;/strong&gt; server, single file, no build step. Holds the ElevenLabs key server-side — the browser never sees it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text extraction:&lt;/strong&gt; &lt;code&gt;pdfjs-dist&lt;/code&gt; for PDFs, &lt;code&gt;tesseract.js&lt;/code&gt; for OCR on photographed letters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The understanding layer:&lt;/strong&gt; a swappable LLM — local Ollama (&lt;code&gt;llama3.2&lt;/code&gt;, $0 cost, runs entirely on-device), or hosted OpenAI/Anthropic — that turns raw, jargon-heavy document text into a structured plain-language breakdown: summary, key points, actions with deadlines, warnings, and glossary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voice:&lt;/strong&gt; ElevenLabs' text-to-speech API, specifically the &lt;code&gt;with-timestamps&lt;/code&gt; endpoint, which returns character-level timing alongside the audio. The frontend maps that real alignment data onto individual word spans, so the highlight isn't a fake proportional estimate — it's actually locked to the audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice input:&lt;/strong&gt; the browser's built-in Speech Recognition API for asking follow-up questions hands-free — no extra key required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar export:&lt;/strong&gt; client-side &lt;code&gt;.ics&lt;/code&gt; generation, only offered when a real, parseable date exists — no guessing at vague deadlines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges I ran into
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real word-level sync is harder than it looks.&lt;/strong&gt; My first pass at highlighting used a length-weighted time estimate per sentence — good enough to demo, but not actually synced to the audio. Rebuilding it on ElevenLabs' character-level alignment data meant reworking the transcript into per-word spans and mapping character offsets back to real timestamps, with a graceful fallback to the old estimate if alignment ever comes back empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A live 402 from ElevenLabs.&lt;/strong&gt; Mid-testing, the app's original default voice started returning &lt;code&gt;payment_required&lt;/code&gt; — turns out ElevenLabs recently restricted "library" voices to paid plans via the API. I caught it, pulled the account's actual available voice list, and swapped in a working free-tier voice ("Sarah") so the app isn't silently broken for the next person who clones it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trustworthy dates, not guessed ones.&lt;/strong&gt; For the calendar export, I deliberately only parse deadlines that resolve to an unambiguous date — "April 22, 2026" gets a calendar button, "within 30 days" doesn't. For a tool handling bills and legal notices, a wrong guess about understanding is worse than no shortcut at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accomplishments I am proud of
&lt;/h2&gt;

&lt;p&gt;Getting the whole pipeline — OCR/PDF extraction → real AI comprehension and plain-language rewriting → real ElevenLabs voice with true word-level sync → calendar export — working end-to-end, verified live against three genuinely different real-world document types, with zero console errors and a recorded video + audio to prove it.&lt;/p&gt;

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

&lt;p&gt;That "passion project" doesn't have to mean something loud. Some of the most stubborn devotion shows up as making sure a scary, confusing envelope never gets the last word — and that understanding, not volume, is the thing that actually needs delivering first.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A caregiver "share this explanation" mode, so an adult child can see exactly what their parent's letter said and meant.&lt;/li&gt;
&lt;li&gt;A lightweight "does this look like a scam?" pass, given how much of this mail is designed to look official whether it is or not.&lt;/li&gt;
&lt;li&gt;True multilingual voice models (&lt;code&gt;eleven_multilingual_v2&lt;/code&gt;) for richer non-English read-alouds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Built with
&lt;/h2&gt;

&lt;p&gt;Node.js · Express · pdfjs-dist · tesseract.js · ElevenLabs (Text-to-Speech + timestamps API) · Ollama (llama3.2) · Web Speech API · vanilla HTML/CSS/JS&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize category: Best use of ElevenLabs
&lt;/h2&gt;

&lt;p&gt;Clarity's core job is comprehension — the real AI-driven breakdown of a confusing document into plain language is what makes any of this useful. But comprehension that stays trapped on a screen doesn't reach everyone it needs to: the whole point is to hand that understanding to people who read poorly, see poorly, or read a different language — and that handoff happens entirely through ElevenLabs. Specifically, I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;with-timestamps&lt;/code&gt; TTS endpoint&lt;/strong&gt;, not just plain audio — its character-level alignment data is what drives real, accurate word-by-word highlighting, rather than a fake time estimate, so the explanation is followable in real time, not just audible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple output languages&lt;/strong&gt; through the same voice pipeline, so the same plain-language explanation can be read back in Spanish, Hindi, Arabic, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice configuration as a first-class setting&lt;/strong&gt; (voice ID, model ID) — and I hit and fixed a real free-tier API policy change along the way, which says something about how deep the integration actually goes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Put My Dying Side Projects on Life Support — an ICU With Real EKGs, a Snowflake Lab, and an On-Chain Defibrillator</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Mon, 13 Jul 2026 03:21:23 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-put-my-dying-side-projects-on-life-support-an-icu-with-real-ekgs-a-snowflake-lab-and-an-32j0</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-put-my-dying-side-projects-on-life-support-an-icu-with-real-ekgs-a-snowflake-lab-and-an-32j0</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;I have lots public repositories. some of them are dead.&lt;/p&gt;

&lt;p&gt;Not deleted — dead. There's a difference. Deleted would mean I made a decision. Dead means one day I committed "fix readme typo" and never came back, and the repo has been lying there ever since, full of half-finished dreams and a &lt;code&gt;TODO.md&lt;/code&gt; I'm afraid to open.&lt;/p&gt;

&lt;p&gt;Everyone builds graveyards for these projects. Post-mortems. Eulogies. I didn't want a graveyard — because my projects aren't dead to me. They're &lt;em&gt;comatose&lt;/em&gt;. So I built the other room in the hospital.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LIFE SUPPORT is an intensive care unit for your side projects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You admit your GitHub username to the ward. Every repo becomes a patient on a live, animated EKG monitor — commit cadence is the heart rate, and projects you've abandoned show the one thing no developer is emotionally prepared to see:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A flatline. With the sound.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdnbs38ue536kytubl23j.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%2Fdnbs38ue536kytubl23j.gif" alt="The monitor wall: every repo on a live EKG" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the lab runs your entire commit history through Snowflake and prints your chart, including the number I was genuinely afraid to learn about myself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My passion half-life: [23] days.&lt;/strong&gt;&lt;br&gt;
The median time it takes my enthusiasm for a new project to decay by 50%. Fitted as an exponential decay curve over my actual weekly commit counts. My love has a measurable half-life, and it is shorter than a gym membership.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The chart also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BPM&lt;/strong&gt; — beats per month. One commit, one heartbeat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 2 AM index&lt;/strong&gt; — [26]% of my commits happen between midnight and 5 AM. That is not a schedule. That is love.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ward census&lt;/strong&gt; — [3] alive, [6] flatlined, [1] critical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longest flatline&lt;/strong&gt; — [&lt;code&gt;crypto-tracker&lt;/code&gt;], silent for [2.2 years], built at the exact top of the market.&lt;/li&gt;
&lt;/ul&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%2Fa0czzwxi9bjylg0zo6g5.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%2Fa0czzwxi9bjylg0zo6g5.png" alt="The diagnosis chart" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then — the part I'm proudest of — the app doesn't let you just &lt;em&gt;feel bad&lt;/em&gt;. Every flatlined patient has a red button:&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚡ DEFIBRILLATE
&lt;/h3&gt;

&lt;p&gt;Pressing it opens a revival pledge on &lt;strong&gt;Solana&lt;/strong&gt;: a memo transaction, signed with your own wallet, containing a vow to ship at least one commit to that repo within 7 days. It's permanent, timestamped, and publicly auditable.&lt;/p&gt;

&lt;p&gt;Here's why a blockchain, sincerely, and not as a sticker: &lt;strong&gt;you can delete a todo list. You can dismiss a calendar reminder. You cannot quietly un-promise something you signed on-chain.&lt;/strong&gt; It's a commitment device. After the deadline, LIFE SUPPORT checks GitHub and renders one of three verdicts: &lt;strong&gt;PLEDGE HONORED&lt;/strong&gt;, &lt;strong&gt;PLEDGE PENDING&lt;/strong&gt;, or — the chain remembers this one too — &lt;strong&gt;PLEDGE BROKEN&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvibwibq6zl9t93gnufdp.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%2Fvibwibq6zl9t93gnufdp.png" alt="The defibrillator: signing a revival pledge" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I signed a pledge for [&lt;code&gt;ai-recipe-bot&lt;/code&gt;], flatlined [267] days. The clock is ticking as you read this. Feel free to check whether I honored it.&lt;/p&gt;

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

&lt;p&gt;Two things to try:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Toggle &lt;strong&gt;SOUND ON&lt;/strong&gt; in the header, then hover a healthy monitor. Then hover a flatlined one. I'm sorry in advance.&lt;/li&gt;
&lt;li&gt;Look at the "when the passion burns" histogram. The red bars are your nights.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full walkthrough:&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%2Ffkv0t5dyyqpg60z5pr5r.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%2Ffkv0t5dyyqpg60z5pr5r.gif" alt="Full demo: admission → diagnosis → defibrillation" width="720" height="450"&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%2Fo26mxx7anszf95fiaww9.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%2Fo26mxx7anszf95fiaww9.png" alt="Diagnosis" width="800" height="500"&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%2Fwzqq23k2ol98gcpl55mi.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%2Fwzqq23k2ol98gcpl55mi.png" alt="Defibrillator" width="800" height="500"&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%2Fgdi9q2b29bhz94p26189.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%2Fgdi9q2b29bhz94p26189.png" alt="Pledge-Signed" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&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/Aryakoste" rel="noopener noreferrer"&gt;
        Aryakoste
      &lt;/a&gt; / &lt;a href="https://github.com/Aryakoste/LifeSupport" rel="noopener noreferrer"&gt;
        LifeSupport
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;🫀 LIFE SUPPORT&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An intensive care unit for your dying side projects.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every repo you abandoned is still in there. LIFE SUPPORT admits your GitHub
account to the ward, puts every side project on a live EKG monitor, runs your
entire commit history through a Snowflake-powered lab, and prints the one
number you are afraid of: your &lt;strong&gt;passion half-life&lt;/strong&gt; — how many days it takes
your love for a new project to decay by half.&lt;/p&gt;
&lt;p&gt;Then it hands you a defibrillator: an on-chain &lt;strong&gt;revival pledge&lt;/strong&gt; on Solana. A
permanent, timestamped, publicly auditable vow to ship one commit within 7
days. You can delete a todo list. You cannot quietly un-promise.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The ward at a glance&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vital&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Passion half-life&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Exponential decay fit over your weekly commit counts: &lt;code&gt;ln(commits+1) ~ a + b·week&lt;/code&gt;, half-life = &lt;code&gt;-ln(2)/b&lt;/code&gt;. Computed per repo, reported as the median.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BPM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Beats per month.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aryakoste/LifeSupport" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Started and finished inside the challenge window, as the rules demand — which felt poetic, because building a project about abandoning projects in 48 hours means racing your own half-life. According to my chart I had at least [23] days of love available. I only needed two.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Next.js 14 (TypeScript) · Snowflake · Solana (devnet, Memo program) · GitHub REST API · hand-rolled canvas EKGs · WebAudio · zero UI libraries.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lab runs on Snowflake — actual SQL, not a sticker (Best Use of Snowflake)
&lt;/h3&gt;

&lt;p&gt;Every commit is ingested into a Snowflake table, and the &lt;strong&gt;entire diagnosis is one SQL statement&lt;/strong&gt;. The parts I enjoyed way too much:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The passion half-life is a linear regression in SQL.&lt;/strong&gt; Log-linearize exponential decay, then let &lt;code&gt;REGR_SLOPE&lt;/code&gt; do the fitting:&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="c1"&gt;-- ln(commits + 1) ~ a + b * week   →   half-life = -ln(2)/b&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REGR_SLOPE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;WK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;weeks_dense&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;REPO&lt;/span&gt;
&lt;span class="c1"&gt;-- later: IFF(B &amp;lt; 0, -LN(2) / B * 7, NULL) AS HALF_LIFE_DAYS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Silent weeks must count.&lt;/strong&gt; A decay fit over only the weeks you &lt;em&gt;did&lt;/em&gt; commit is flattery. I densify the calendar with a &lt;code&gt;GENERATOR&lt;/code&gt; so every silent week shows up as a zero — the silence is the data:&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WK&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="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&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;AS&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&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;SEQ4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;WK&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GENERATOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROWCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WK&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FIRST_C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LAST_C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;weekly&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REPO&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Commit streaks are gaps-and-islands.&lt;/strong&gt; The old window-function party trick: day number minus row number is constant within a consecutive run:&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="n"&gt;DATEDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1970-01-01'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ROW_NUMBER&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;OVER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;REPO&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;GRP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a local TypeScript engine implementing the identical math, so the demo works with zero setup — both engines agree on a toy dataset to the decimal (13.5 days, textbook new-project decay). Same math, two labs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The defibrillator runs on Solana (Best Use of Solana)
&lt;/h3&gt;

&lt;p&gt;The pledge is a Memo-program transaction signed by the user's Phantom wallet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LIFE-SUPPORT/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REVIVAL_PLEDGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-recipe-bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surgeon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[you]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I will ship at least one commit before the deadline."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deadline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+7 days"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I deliberately did &lt;strong&gt;not&lt;/strong&gt; write a custom program for this — a memo is exactly enough, and choosing the smallest on-chain footprint that delivers the property you actually need (immutable public accountability) felt more honest than deploying a contract for the résumé value. Verification happens off-chain against the GitHub API. No wallet installed? You get a clearly-labeled &lt;em&gt;practice pledge&lt;/em&gt; so the flow is never gated.&lt;/p&gt;

&lt;h3&gt;
  
  
  The EKGs are honest
&lt;/h3&gt;

&lt;p&gt;The monitors are a hand-written &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; renderer that draws the way real patient monitors draw — a moving write head with an erase bar, sweeping at ~25mm/s, P-QRS-T complexes and all. The waveform shape is decorative physiology; &lt;strong&gt;the rate, the status, and every number on the chart are real&lt;/strong&gt;, derived from real commit history. Flatlines are perfectly flat. Critical patients beat weak and slow. It respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt; and the beeps are WebAudio, off by default, because I like whimsy but I'm not a monster.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learned about myself
&lt;/h3&gt;

&lt;p&gt;My commit history knew things about me that I didn't. My peak commit hour is [02:00]. My passion decays by half every [23] days — but &lt;code&gt;dotfiles&lt;/code&gt;, unglamorous, six years old, never decayed at all. Apparently what survives isn't the exciting projects. It's the ones stitched into how I live.&lt;/p&gt;

&lt;p&gt;Also: hovering over your own flatlined repo while the tone plays is a genuinely strange emotional experience, and I built the thing.&lt;/p&gt;

&lt;p&gt;One repo gets my first revival pledge. The chain is watching now. So are you.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Best Use of Snowflake&lt;/strong&gt;&lt;br&gt;
Snowflake is the hospital lab — every diagnosis on the chart is computed as SQL inside the warehouse, not in application code.&lt;br&gt;
The app ingests your full GitHub commit history into a Snowflake table, then runs the entire patient diagnosis as one SQL statement. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Solana&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solana is the defibrillator — not a badge, but a commitment device.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every flatlined repo gets a ⚡ DEFIBRILLATE button. Pressing it signs a Memo-program transaction on devnet with your Phantom wallet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LIFE-SUPPORT/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REVIVAL_PLEDGE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-recipe-bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"surgeon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[you]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I will ship at least one commit before the deadline."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deadline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"+7 days"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why a blockchain, sincerely: you can delete a todo list. You can dismiss a calendar reminder. You cannot quietly un-promise something you signed on-chain. The properties I actually needed — immutability, timestamping, public auditability — are precisely the properties Solana provides. A private database would let me lie to myself, which is the exact failure mode the app exists to fix.&lt;/p&gt;




&lt;p&gt;Run yours — I'd honestly love to see your half-life in the comments. ([Repo half-lives under 15 days get automatic sympathy.])*&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>I built an AI commentator that screams about your sourdough like it's the World Cup final</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Mon, 13 Jul 2026 02:43:54 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-built-an-ai-commentator-that-screams-about-your-sourdough-like-its-the-world-cup-final-34o2</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-built-an-ai-commentator-that-screams-about-your-sourdough-like-its-the-world-cup-final-34o2</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;Last week my grandpa beat me at chess. Again. He slid his rook across the board, said absolutely nothing, and went to make tea. And I thought: &lt;em&gt;this man just hit me with a tactic he's been setting up for eleven moves, and the only sound in the room was a kettle.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, on TV, a man kicked a ball two meters into an open net and a commentator screamed for thirty consecutive seconds, lost his voice, found it again, and thanked his mother.&lt;/p&gt;

&lt;p&gt;That gap felt unfair. So I closed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GOLAZO&lt;/strong&gt; is an AI hype commentator for your life. You describe a moment — any moment — and it gets called like the winning goal of a World Cup final:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick your booth: &lt;strong&gt;The Legend&lt;/strong&gt; (measured British gravitas that erupts at exactly the right beat), &lt;strong&gt;La Radio&lt;/strong&gt; (Latin American radio at full throttle — the GOOOOOL is contractually mandatory), or &lt;strong&gt;The Poet&lt;/strong&gt; (golden-age radio lyricism that treats your laundry like the moon landing).&lt;/li&gt;
&lt;li&gt;Pick one of &lt;strong&gt;nine languages&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Crank the &lt;strong&gt;intensity dial&lt;/strong&gt;. It goes to 10. It should probably not go to 10. It goes to 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then Gemini writes the commentary, it streams onto the screen word-by-word next to a blinking REC dot like a live feed, and ElevenLabs stands up in the booth and &lt;strong&gt;roars it out loud&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's what it did with my grandpa:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"...Grandpa — seventy-eight years young, cardigan flapping like a cape — slides the rook home! He's done it again! The pawns are weeping, the knights bow their heads, and somewhere a kettle whistles in triumph! CHECKMAAAAAAATE! Legends never retire, my friends — they just play the long game!"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He listened to it twice. He did not smile. He is The Legend.&lt;/p&gt;

&lt;p&gt;There's a sincere idea hiding under the joke, though. Passion in football &lt;em&gt;is&lt;/em&gt; the commentator — the voice is what turns a ball crossing a line into something people cry about. But that voice only exists for ninety televised minutes. Kids' leagues don't get one. Grassroots matches don't get one. Visually impaired fans watching anything below the top flight usually don't get one. GOLAZO is a tiny, silly step toward a world where &lt;strong&gt;every moment gets a voice in the booth&lt;/strong&gt; — including the small ones. &lt;em&gt;Especially&lt;/em&gt; the small ones.&lt;/p&gt;

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

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

&lt;p&gt;Here's the booth going live — describe the moment, pick La Radio, dial it to 10, and watch the commentary stream in:&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%2F439z9zct21ks72ii7oku.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%2F439z9zct21ks72ii7oku.gif" alt=" " width="720" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it yourself: tap the preset chip &lt;strong&gt;"Grandpa beat me at chess again"&lt;/strong&gt;, choose La Radio, intensity 10, and press &lt;em&gt;Hear it&lt;/em&gt;. I take no responsibility for what your speakers do next.&lt;/p&gt;

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

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

&lt;p&gt;The entire application is &lt;strong&gt;one &lt;code&gt;index.html&lt;/code&gt; file&lt;/strong&gt;. No framework. No build step. No backend. No &lt;code&gt;node_modules&lt;/code&gt; folder achieving sentience. You could email this app to someone. Please don't, but you could.&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%2Fjiozq4bbgj9ry78mz8vk.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%2Fjiozq4bbgj9ry78mz8vk.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The design is a broadcast, not an app.&lt;/strong&gt; Chalk white, pitch green, ticker yellow, hard black keylines on everything, and Anton at a full italic skew because passion does not stand up straight. There's a scrolling ticker at the top and a blinking REC dot on the output, and the commentary doesn't just &lt;em&gt;appear&lt;/em&gt; — it types itself out at broadcast pace, because passion delivered instantly is a paragraph, but passion delivered word-by-word is an &lt;strong&gt;event&lt;/strong&gt;. (Ticker, blink, and typewriter all switch off under &lt;code&gt;prefers-reduced-motion&lt;/code&gt;. Hype should be opt-in.)&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%2Fd6kr1xh0mfhzihn123p9.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%2Fd6kr1xh0mfhzihn123p9.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google AI (Gemini 2.0 Flash)&lt;/strong&gt; writes every line via the &lt;code&gt;generateContent&lt;/code&gt; REST API, called straight from the browser with the user's own free key (stored only in localStorage — the only servers your data ever touches are Google's and ElevenLabs' own). The whole project lives or dies in one prompt. Each booth has a personality brief — La Radio's literally includes &lt;em&gt;"the climactic word MUST be an extended GOOOOOL-style cry adapted to the moment"&lt;/em&gt; — plus the language, the intensity scale, a strict 60–110 word budget, and the single rule I care most about:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never mock the smallness of the moment. Its smallness is what makes it great.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without that line, an LLM defaults to gentle irony — it commentates your sourdough with a smirk. With it, the output is &lt;em&gt;sincere&lt;/em&gt;, and sincerity is the entire product. Your bread deserves a believer, not a comedian.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElevenLabs&lt;/strong&gt; (&lt;code&gt;eleven_multilingual_v2&lt;/code&gt;) is the voice, and this took actual tuning. Default voice settings sound like someone reading a weather report. I dropped &lt;code&gt;stability&lt;/code&gt; to 0.35 and pushed &lt;code&gt;style&lt;/code&gt; to 0.7, and suddenly the delivery got loose, fast, and genuinely excited — less "newsreader," more "man standing on his chair." The multilingual model means a Spanish La Radio call actually &lt;em&gt;sounds&lt;/em&gt; like Spanish radio. And if a visitor has no ElevenLabs key, the Web Speech API steps in as the backup announcer, so the demo works for absolutely everyone.&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%2F1zncvwo8vdq1smngmhj5.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%2F1zncvwo8vdq1smngmhj5.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your last eight commentaries are saved locally as &lt;strong&gt;Match Highlights&lt;/strong&gt;, because some victories deserve a replay.&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%2Fg0v6c0hkkn9nkm1ygsjl.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%2Fg0v6c0hkkn9nkm1ygsjl.png" alt=" " width="780" height="1688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The hardest bug of the weekend, for the record, was not the AI. It was CSS. It is always CSS.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Google AI&lt;/strong&gt; — Gemini writes every commentary from a style-briefed, intensity-scaled, sincerity-enforced prompt, in nine languages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — expressive multilingual TTS, deliberately de-tuned from "newsreader" to "commentator who has completely lost his composure."&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Now if you'll excuse me, I have a rematch to lose. Intensity 10 is standing by.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>QUERY: the HTTP method we've been faking for 16 years</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Fri, 03 Jul 2026 08:22:17 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/query-the-http-method-weve-been-faking-for-16-years-f9i</link>
      <guid>https://dev.to/arya_koste_5845807df94776/query-the-http-method-weve-been-faking-for-16-years-f9i</guid>
      <description>&lt;p&gt;HTTP just got its first brand-new method since 2010. Meet QUERY, a GET with a body, or a POST that's finally honest about being read-only.&lt;/p&gt;

&lt;p&gt;Every backend dev has hit this moment. You've got a search endpoint. The filters are getting spicy: nested facets, an array of tags, a sort order, a date range. You reach for &lt;code&gt;GET&lt;/code&gt;, because searching is reading and reading is a &lt;code&gt;GET&lt;/code&gt;, obviously.&lt;/p&gt;

&lt;p&gt;Then your query object grows up, your URL crosses two thousand characters, some proxy three hops away quietly truncates it, and you spend an afternoon learning which piece of middleware hates you today.&lt;/p&gt;

&lt;p&gt;So you give up and switch to &lt;code&gt;POST&lt;/code&gt;. It works. But now you're lying. &lt;code&gt;POST&lt;/code&gt; tells every cache, CDN, and proxy on the wire, "careful, this might &lt;em&gt;change something&lt;/em&gt;," when all you did was ask a question. No caching for you.&lt;/p&gt;

&lt;p&gt;We've been stuck choosing between a verb that can't carry luggage and a verb that sets off the alarm at every checkpoint. For sixteen years there was no third option.&lt;/p&gt;

&lt;p&gt;Now there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet QUERY
&lt;/h2&gt;

&lt;p&gt;In June 2026 the IETF published &lt;strong&gt;RFC 10008&lt;/strong&gt;, standardizing a new HTTP method called &lt;code&gt;QUERY&lt;/code&gt;. It's the first genuinely new standard HTTP method since &lt;code&gt;PATCH&lt;/code&gt; landed back in 2010, which in web-standards time is roughly an ice age.&lt;/p&gt;

&lt;p&gt;The pitch is almost annoyingly simple. &lt;code&gt;QUERY&lt;/code&gt; takes the request body from &lt;code&gt;POST&lt;/code&gt; and the good manners from &lt;code&gt;GET&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It &lt;strong&gt;carries a body&lt;/strong&gt;, so your gnarly filter object goes where it belongs.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;safe&lt;/strong&gt;: the server promises not to change state.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;idempotent&lt;/strong&gt;: fire it twice, get the same result, no harm done.&lt;/li&gt;
&lt;li&gt;And because of those two promises, it's &lt;strong&gt;cacheable&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the quiet superpower. A cache can look at a &lt;code&gt;QUERY&lt;/code&gt;, see that it's safe and idempotent, and reuse the response, something it could never responsibly do with a &lt;code&gt;POST&lt;/code&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%2Ft9dola8o9r2486sbm4bn.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%2Ft9dola8o9r2486sbm4bn.png" alt="Three verbs, four properties: GET, POST, and QUERY compared across body support, safety, idempotency, and cacheability." width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually looks like
&lt;/h2&gt;

&lt;p&gt;Nothing exotic. It's a request line you already know how to read, just with a new verb at the front and a body hanging off the bottom:&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="nf"&gt;QUERY&lt;/span&gt; &lt;span class="nn"&gt;/products&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.shop.example&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boots"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"inStock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Content-Type&lt;/code&gt; isn't decoration. It tells the server what &lt;em&gt;language&lt;/em&gt; your query is written in. JSON here, but nothing stops a resource from speaking JSONPath, a SQL-ish dialect, or some format your team invented on a whiteboard. The URL identifies &lt;em&gt;what&lt;/em&gt; you're querying; the body says &lt;em&gt;how&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%2F8ha8zukdvbfww19f962u.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%2F8ha8zukdvbfww19f962u.png" alt="Annotated QUERY request and response, showing the method line, the body-as-query, and the Accept-Query response header" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the response header:&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="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Accept-Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;Cache-Control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;max-age=60, private&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Accept-Query&lt;/code&gt; is the other new thing the RFC introduces. It lets a resource advertise which query formats it understands, the same way &lt;code&gt;Accept-Post&lt;/code&gt; works for &lt;code&gt;POST&lt;/code&gt;. Self-documenting endpoints, for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, haven't we seen this before?
&lt;/h2&gt;

&lt;p&gt;Kind of! If &lt;code&gt;QUERY&lt;/code&gt; feels familiar, that's because its great-grandparent is the old WebDAV &lt;code&gt;SEARCH&lt;/code&gt; method. The idea of "a read that carries a body" has been circling the standards world for two decades. It just never made it into the HTTP core vocabulary, not until the working group dusted it off, sanded down the rough edges, and gave it a name that says exactly what it does.&lt;/p&gt;

&lt;p&gt;And honestly, we've all been &lt;em&gt;simulating&lt;/em&gt; &lt;code&gt;QUERY&lt;/code&gt; for years. Elasticsearch let you send a body with &lt;code&gt;GET /_search&lt;/code&gt; (technically undefined behavior that half the tooling refuses to send) and also offered &lt;code&gt;POST /_search&lt;/code&gt; for when that broke. That awkward dance, where you pick your poison and give up either the body or the caching, is precisely the gap &lt;code&gt;QUERY&lt;/code&gt; fills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one gotcha: caching keys
&lt;/h2&gt;

&lt;p&gt;Here's the detail that'll bite the unwary. When a cache stores a normal &lt;code&gt;GET&lt;/code&gt;, the cache key is basically the URL. Two identical URLs, same cached response.&lt;/p&gt;

&lt;p&gt;But two &lt;code&gt;QUERY&lt;/code&gt; requests to the &lt;em&gt;same URL&lt;/em&gt; can have completely different bodies. That's the whole point. So a compliant cache &lt;strong&gt;must fold the request body into the cache key&lt;/strong&gt;. If your cache or CDN doesn't understand &lt;code&gt;QUERY&lt;/code&gt; yet, it might key on the URL alone and cheerfully hand user B the results of user A's query. Fun bug. Do not ship it.&lt;/p&gt;

&lt;p&gt;The RFC is explicit about this, but your infrastructure has to actually implement it. Which brings us to the part where I gently pump the brakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you rush this into production?
&lt;/h2&gt;

&lt;p&gt;No. Love the enthusiasm, but no.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; is a &lt;em&gt;proposed standard&lt;/em&gt;, published in mid-2026. That means the spec is solid, but the ecosystem is still catching up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browsers don't send &lt;code&gt;QUERY&lt;/code&gt; from &lt;code&gt;fetch()&lt;/code&gt; everywhere yet, and HTML &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; still only knows &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Plenty of proxies, WAFs, and API gateways drop or reject methods they don't recognize.&lt;/li&gt;
&lt;li&gt;Cache implementations that key &lt;code&gt;QUERY&lt;/code&gt; correctly are the exception, not the rule today.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Support like this spreads in years, not weeks. &lt;code&gt;PATCH&lt;/code&gt; was standardized in 2010 and &lt;em&gt;still&lt;/em&gt; trips over the occasional stubborn gateway.&lt;/p&gt;

&lt;p&gt;So treat &lt;code&gt;QUERY&lt;/code&gt; the way you'd treat any shiny new protocol feature: understand it, design your API vocabulary with it in mind, maybe reach for it behind your own controlled infrastructure where you own the whole path. But keep that &lt;code&gt;POST /search&lt;/code&gt; endpoint around for a while yet.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;QUERY&lt;/code&gt; isn't flashy. It doesn't unlock anything you literally couldn't do before. What it does is end a sixteen-year-old compromise. It lets you say, right there in the protocol, &lt;em&gt;"I'm asking a question, not changing anything, and here's my complicated question in the body."&lt;/em&gt; And for the first time, the caches and proxies on the wire can actually understand you.&lt;/p&gt;

&lt;p&gt;It's a small verb with a big job: making read-only requests honest again.&lt;/p&gt;

&lt;p&gt;Now if you'll excuse me, I have a few &lt;code&gt;POST /search&lt;/code&gt; endpoints to feel guilty about.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you started experimenting with &lt;code&gt;QUERY&lt;/code&gt; yet, or are you waiting for the ecosystem to catch up? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>backend</category>
      <category>http</category>
    </item>
    <item>
      <title>Filtered Vector Search: Where Every Benchmark Quietly Lies to You</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Thu, 02 Jul 2026 17:52:09 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/filtered-vector-search-where-every-benchmark-quietly-lies-to-you-36kj</link>
      <guid>https://dev.to/arya_koste_5845807df94776/filtered-vector-search-where-every-benchmark-quietly-lies-to-you-36kj</guid>
      <description>&lt;p&gt;Let me show you a magic trick every vector database benchmark performs.&lt;/p&gt;

&lt;p&gt;Step one: run a query with no filter. Just "find me the nearest neighbors to this vector." Step two: report the gorgeous QPS numbers and the 95% recall. Step three: take a bow while everyone claps.&lt;/p&gt;

&lt;p&gt;Here is the query you actually run in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nearest neighbors to &amp;lt;embedding&amp;gt;
WHERE tenant_id = 'acme'
  AND status = 'active'
  AND created_at &amp;gt; '2026-01-01'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is where the applause stops. Latency triples. Recall quietly falls off a cliff. You ask for ten results and get three, and nobody notices until a user files a ticket that just says "search feels broken." No stack trace. No error. Just vibes, and the vibes are bad.&lt;/p&gt;

&lt;p&gt;This is filtered vector search, and it is the single most under-discussed part of the whole retrieval stack. People will argue for three hours about pgvector versus Pinecone and then hand-wave the one thing that actually decides whether their search works: what happens when you bolt a &lt;code&gt;WHERE&lt;/code&gt; clause onto an approximate nearest neighbor search.&lt;/p&gt;

&lt;p&gt;So let's talk about it. Not which database to buy. The actual mechanics of why filtering a vector search is weirdly, unintuitively hard, the three ways to do it, why two of them fail in exactly opposite situations, and the newer trick that makes a filter actually speed your search &lt;em&gt;up&lt;/em&gt;. Stick around for that last part, it's genuinely delightful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your SQL instincts are lying to you
&lt;/h2&gt;

&lt;p&gt;Quick gut check. In a normal database, does adding &lt;code&gt;WHERE status = 'active'&lt;/code&gt; make a query faster or slower?&lt;/p&gt;

&lt;p&gt;Faster, obviously. You've got an index on &lt;code&gt;status&lt;/code&gt;, you look up the matching rows, you scan less stuff. Filtering is a discount. This instinct has served you well for your entire career.&lt;/p&gt;

&lt;p&gt;Throw it in the bin. It does not apply here, and understanding why is the whole game.&lt;/p&gt;

&lt;p&gt;Vector search over millions of embeddings is only fast because of a structure like HNSW (Hierarchical Navigable Small World). Picture a graph where every vector is a dot, and each dot is wired to its nearest neighbors. A search drops in at an entry point and walks the graph greedily, hopping dot to dot toward your query vector, touching a tiny sliver of the total data. That walk is the magic. It's what turns "compare against all 10 million vectors" into "visit maybe 1,200 of them."&lt;/p&gt;

&lt;p&gt;Now add a filter. Suddenly a bunch of dots are off-limits because they don't match &lt;code&gt;tenant_id = 'acme'&lt;/code&gt;. But here's the thing: the graph's wiring was built assuming every dot is fair game. Rip out the forbidden dots and you punch holes in the connectivity the walk depends on. The route the search needed might run straight through nodes that no longer exist.&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%2F2mzzbyn5qgdhobcpwity.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%2F2mzzbyn5qgdhobcpwity.png" alt="HNSW graph before and after a filter: on the left a clean path runs from entry to target; on the right, filtered-out nodes sever that path" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the right side. The path the search wanted to take got cut because the filter deleted the stepping stones. The search is now standing on one side of a river that used to have a bridge. That's the core tension of this entire topic: the index that makes vector search fast assumes nobody's filtering, and every filter is a small betrayal of that assumption.&lt;/p&gt;

&lt;p&gt;There are three ways to cope. They are not equally good, and the two obvious ones fail in beautifully opposite ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three strategies, at a glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F19huas4yx6axg76a4hc4.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%2F19huas4yx6axg76a4hc4.png" alt="Three strategies compared: post-filtering searches then discards and breaks on restrictive filters; pre-filtering filters then searches and breaks on large subsets; filter-aware filters during traversal and handles the messy middle" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep this picture in your head as we go. Post-filtering breaks on the right side of the selectivity range, pre-filtering breaks on the left, and only the third one covers the middle where your real queries live. Now the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 1: Post-filtering (the "search now, apologize later" approach)
&lt;/h2&gt;

&lt;p&gt;The obvious move. Run the normal vector search, get your candidates, then chuck the ones that don't match.&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="c1"&gt;# Post-filtering
&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ann_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# filter? what filter?
&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. No special index magic required. And it's totally fine as long as your filter is loose, meaning most of the dataset passes it. If 80% of your rows are &lt;code&gt;active&lt;/code&gt;, your top 100 candidates will contain plenty of active ones. Everybody's happy.&lt;/p&gt;

&lt;p&gt;Now watch it detonate. Suppose only 1% of your points match. You pull 100 candidates from the unfiltered search, and on average... one of them is &lt;code&gt;active&lt;/code&gt;. You asked for ten results. You got one. The actual ten closest active vectors? Never in the running. They were sitting further out in the graph, politely waiting behind a wall of inactive points that hogged your top 100.&lt;/p&gt;

&lt;p&gt;"Just fetch more," you say, sweat forming. "Grab the top 1,000." Sure, sometimes that rescues you. You're also now doing 10x the search work, your latency is climbing, and against a really nasty filter you're still just gambling with extra steps. Over-fetching isn't a fix, it's a bribe you pay the problem to leave you alone, and the bribe scales with exactly how selective your filter is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Post-filtering dies when filters are restrictive.&lt;/strong&gt; The more you filter, the more it flails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 2: Pre-filtering (the "opposite mistake")
&lt;/h2&gt;

&lt;p&gt;Fine, do the reverse. Find everything matching the filter first, then search only inside that subset.&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="c1"&gt;# Pre-filtering
&lt;/span&gt;&lt;span class="n"&gt;allowed_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;metadata_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# inverted index, etc.
&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ann_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;restrict_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;allowed_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a restrictive filter this sounds perfect, and for &lt;em&gt;small&lt;/em&gt; subsets it genuinely is chef's kiss. If the filter narrows you down to a few thousand vectors, just brute-force the search over those and get exact, high-recall results, fast. This is exactly why good engines have a "if the subset is tiny, skip the fancy graph and just scan it" fallback. Correct call.&lt;/p&gt;

&lt;p&gt;But scale that allowed set up and the whole thing flips on you. Remember the graph runs on connectivity. When you restrict the walk to a subset, you're yanking all the non-matching dots out mid-search. On a big dataset where the filter still matches, say, a few hundred thousand scattered points, you fragment the graph exactly like our first diagram: the greedy walk keeps trying to route through excluded nodes, hits dead ends, and either returns garbage or collapses into something slow. The logarithmic magic is gone. You're back to expensive.&lt;/p&gt;

&lt;p&gt;So pre-filtering is the exact mirror image of post-filtering. Post handles loose filters and dies on tight ones. Pre handles tiny result sets and dies on large ones. Which leaves a lovely canyon in the middle, a moderately selective filter over a large collection, where both approaches whimper. Guess where basically every real production query lives? The canyon. Everyone lives in the canyon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-filtering dies when the filtered subset is large and scattered.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy 3: Filter-aware search (the one that actually works)
&lt;/h2&gt;

&lt;p&gt;The grown-up answer is to stop treating the filter as a thing that happens before or after the search, and fold it &lt;em&gt;into&lt;/em&gt; the walk itself. The graph traversal still happens, but now the filter rides along and steers: when the search picks which neighbor to hop to next, it's already accounting for the filter instead of ignoring it (post) or being blindly walled off by it (pre).&lt;/p&gt;

&lt;p&gt;Two flavors worth knowing, because they map onto real toggles you'll flip in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter-aware edges, built ahead of time.&lt;/strong&gt; One approach adds extra edges to the graph based on the fields you'll filter on. For a field like &lt;code&gt;category&lt;/code&gt;, the index quietly builds connected subgraphs per value, so when you later filter &lt;code&gt;category = 'laptop'&lt;/code&gt;, there's already a fully-connected laptop-only graph to walk. Search quality holds up even under tight filters, with basically no runtime tax, because the work happened at build time. The catch is combinations: the index can pre-wire &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;brand&lt;/code&gt; separately, but it can't pre-wire every &lt;code&gt;category AND brand AND price&lt;/code&gt; combo, the math explodes. So one strict filter is smooth, but stack two or three high-selectivity filters and you can still fragment the graph, just fashionably later than you would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adaptive traversal, decided at query time.&lt;/strong&gt; A newer family (the one you'll hear named in 2026 is ACORN) skips the "know your filters in advance" requirement. The trick: during the walk, if a node's direct neighbors are all filtered out, don't quit, peek at the neighbors &lt;em&gt;of&lt;/em&gt; those neighbors. A two-hop jump. This lets the search vault over filtered-out regions and stay connected even for filter combos it never trained for. The honest cost is that exploring more nodes takes more time per query, so you switch this on for the genuinely gnarly cases (complex, unpredictable, low-selectivity filters), not for every lookup. In practice it shows up as a per-query flag, precisely because it's a scalpel, not a bulldozer.&lt;/p&gt;

&lt;p&gt;This is the only strategy that covers the canyon. And it comes with a payoff that feels like cheating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good part: a filter that makes search &lt;em&gt;faster&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;With the naive strategies, filtering is pure tax. More work, more latency, more sighing. With real filter-aware search, the opposite can happen: when the filter genuinely shrinks the pool the search has to consider, and the engine prunes that work &lt;em&gt;during&lt;/em&gt; the walk instead of after, adding a filter can &lt;em&gt;lower&lt;/em&gt; your latency below the unfiltered query.&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%2F1w7h9sfeac5bn7micpig.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%2F1w7h9sfeac5bn7micpig.png" alt="Latency versus filter selectivity: as filters tighten, post-filtering latency climbs, pre-filtering stays flat then fine on tiny sets, and filter-aware latency drops because pruning means less work" width="799" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at that green line going &lt;em&gt;down&lt;/em&gt;. Reported behavior on integrated engines follows this shape: an unfiltered query sits at some baseline, a 50%-selective filter pulls latency down, and a 1%-selective filter pulls it down further, because the search is simply doing less. That downward slope is the tell of a real filter-aware implementation. It's also a fantastic diagnostic when you're kicking the tires on an engine:&lt;/p&gt;

&lt;p&gt;Run your workload filtered, then unfiltered. Watch which way latency moves. If filtering your data makes it &lt;em&gt;faster&lt;/em&gt;, the engine is filtering inside the algorithm, that's the good stuff. If filtering reliably makes it slower, it's quietly doing pre- or post-filtering behind your back, and you should plan accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotchas nobody puts in the quickstart
&lt;/h2&gt;

&lt;p&gt;Understanding the strategies is half the battle. The other half is the boring operational stuff that silently wrecks filtered search and never appears in a "hello world" tutorial. Here's where the bodies are buried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index your filter fields, and do it BEFORE building the vector index.&lt;/strong&gt; This one bites everyone once. Filter-aware edges can only be built if the engine knows which fields you'll filter on at build time. Add a metadata index &lt;em&gt;after&lt;/em&gt; the vector graph is already built and the filter-aware edges don't magically appear, you have to rebuild the vector index to get them. Rebuilding a graph on a big collection is not a coffee break, it's a "come back after lunch" operation. Order of operations: define your filterable fields, index them, &lt;em&gt;then&lt;/em&gt; ingest and build. Future you will send a thank-you card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cardinality changes everything, and the engine may swap strategies mid-flight.&lt;/strong&gt; Good engines estimate how many points a filter will match and pick a strategy per query off that estimate: brute-force the tiny subsets, use the filterable graph for the big ones. Great when it works, but it means your latency profile can shift depending on the specific values a user sends. A filter matching 50 rows and one matching 500,000 rows may run completely different code paths under the hood. So benchmark across the whole range of selectivities you expect, not just one tidy representative query that makes your graphs look nice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch for correlated filters, the silent killer.&lt;/strong&gt; The genuinely brutal case is when your filter is &lt;em&gt;negatively correlated&lt;/em&gt; with vector distance, when the things that match your filter happen to sit far from the query in embedding space. Filter-aware traversal quietly assumes matching points are scattered reasonably near the query. When they're systematically not, even the clever two-hop tricks degrade. If your filtered searches are mysteriously bad and it's not a cardinality thing, ask whether your filter and your embeddings are pulling in opposite directions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-fetching hides bugs.&lt;/strong&gt; If you "solved" filtered search by cranking your candidate count to the moon, congratulations, you've buried the problem, not fixed it, and you're paying the latency tax on every single query forever. Every so often, check whether you still need that over-fetch or whether proper filter-aware indexing would let you quietly delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A cheat sheet for which strategy you actually want
&lt;/h2&gt;

&lt;p&gt;You usually won't hand-code these, the engine picks, but knowing which one you &lt;em&gt;want&lt;/em&gt; tells you what to configure and what to test.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loose filter (matches most of the data):&lt;/strong&gt; post-filtering is fine. Go outside, touch grass, don't overthink it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny subset (a few thousand points):&lt;/strong&gt; you want the brute-force fallback. Make sure your engine's threshold for triggering it is tuned so it actually fires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderately selective filter over a large collection (the canyon, aka real life):&lt;/strong&gt; filter-aware indexing, with your filter fields indexed up front. This is the setup most production RAG needs and the one people most reliably botch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple strict filters, or filters you can't predict:&lt;/strong&gt; this is where query-time adaptive traversal (the two-hop family) earns its keep. Switch it on for these queries specifically. Don't pay its cost on everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one-line takeaway
&lt;/h2&gt;

&lt;p&gt;The database war everyone loves to fight matters less than the layer underneath it. Two engines with near-identical unfiltered benchmarks can behave like completely different products the second you add a &lt;code&gt;WHERE&lt;/code&gt; clause, and your production queries basically always have a &lt;code&gt;WHERE&lt;/code&gt; clause. So when you're evaluating anything for real retrieval work, filter it the way your app actually will, and measure both latency &lt;em&gt;and&lt;/em&gt; recall against the true filtered nearest neighbors.&lt;/p&gt;

&lt;p&gt;Because here's the thing to tattoo somewhere visible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unfiltered benchmarks measure a query you will never actually run.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Fine print on the numbers and named techniques: filtered ANN is an active research area as of 2026, and the specific behaviors here (two-hop traversal, per-value subgraphs, cardinality-based planning) are implemented differently across engines and still evolving. Treat this as the conceptual map, then verify the exact behavior against your engine's current docs and, more importantly, against your own data. The selectivity numbers are illustrative of the pattern, not universal constants.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vectordatabase</category>
      <category>database</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>I gave a brand-new AI the memory of all my old chats, here's the free tool I built to do it.</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Wed, 01 Jul 2026 04:40:06 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-gave-a-brand-new-ai-the-memory-of-all-my-old-chats-heres-the-free-tool-i-built-to-do-it-1550</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-gave-a-brand-new-ai-the-memory-of-all-my-old-chats-heres-the-free-tool-i-built-to-do-it-1550</guid>
      <description>&lt;p&gt;My AI conversations were scattered across three apps that couldn't remember each other. So I built a private, local-first vault that pulls them together and lets me hand my whole history to any new model. Come break it and tell me what's missing.&lt;/p&gt;

&lt;p&gt;Picture this. You spend an hour with ChatGPT untangling a gnarly bug. It's beautiful. You feel understood.&lt;/p&gt;

&lt;p&gt;Two weeks later you need that exact solution again. You open ChatGPT and... which of your 300 conversations was it? Was it even ChatGPT? Maybe it was Claude. Or that one Gemini chat you'll never find because it's titled "Untitled."&lt;/p&gt;

&lt;p&gt;And here's the part that finally broke me: even when you &lt;em&gt;do&lt;/em&gt; find it, the new model you're chatting with today has no idea any of it ever happened. Every AI lives in its own little walled garden with the long-term memory of a goldfish. Switch tools and you start from zero.&lt;/p&gt;

&lt;p&gt;So I did the reasonable, well-adjusted thing and built an app about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet ChatVault
&lt;/h2&gt;

&lt;p&gt;ChatVault is a private home for your AI chat history. You export your conversations from ChatGPT, Claude, or Gemini (they all offer this), drop the file in, and suddenly everything lives in one place you can actually search.&lt;/p&gt;

&lt;p&gt;That alone scratched my itch. But then I added the feature I can't shut up about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fun part: give any AI your memories
&lt;/h2&gt;

&lt;p&gt;There's a button called &lt;strong&gt;Export as memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You pick a bunch of conversations or a whole collection and ChatVault bundles them into a single tidy file with a little instruction note at the top: &lt;em&gt;"Treat the following as background knowledge about me and my projects."&lt;/em&gt; Paste that into a fresh ChatGPT, Claude, or Gemini chat, and the new model starts the conversation already knowing your context.&lt;/p&gt;

&lt;p&gt;It's like handing the new hire a binder of everything the last one learned, instead of making them figure out who you are from scratch every single morning.&lt;/p&gt;

&lt;p&gt;It even trims the fat if you want, strips code blocks, condenses long-winded replies, and shows you a live token estimate so your "memory" actually fits in the model's context window. And because it pulls in the private notes you jot on each chat, the AI gets your &lt;em&gt;curated&lt;/em&gt; takeaways, not just a raw transcript wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else is in the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One search bar&lt;/strong&gt; across every conversation from every tool, with matches highlighted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collections&lt;/strong&gt; to group chats by project (so "Memory export" can target just the right ones)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find in chat&lt;/strong&gt; for jumping around inside a long conversation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bookmarked messages&lt;/strong&gt; - star the good replies, find them later in one view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A snippet library&lt;/strong&gt; that auto-collects every code block you've ever gotten&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insights&lt;/strong&gt; - your most-used words, busiest days, and habits, computed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password-protected backups&lt;/strong&gt; with real encryption, because it's your history&lt;/li&gt;
&lt;li&gt;Dark mode, keyboard shortcuts, works offline
## The catch (there isn't one, and that's the point)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the thing I'm weirdly proud of: &lt;strong&gt;nothing leaves your device.&lt;/strong&gt; No account. No server. No analytics. No "we value your privacy" popup that means the opposite. Your conversations sit in your own browser and never phone home, ChatVault makes zero network requests, and you can watch the Network tab to prove it.&lt;/p&gt;

&lt;p&gt;That's not just a nice-to-have. It's &lt;em&gt;why&lt;/em&gt; it can stay free. There's no server bill to pass on to you, because there's no server. Turns out the cheapest backend is no backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, now the ask
&lt;/h2&gt;

&lt;p&gt;I built this on nights and weekends, which means it works great on my machine and I would love to know how spectacularly it breaks on yours. Especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the import work with your export? (Gemini's format is the wild one.)&lt;/li&gt;
&lt;li&gt;Is the &lt;strong&gt;Export as memory&lt;/strong&gt; file actually useful when you paste it into a new AI?&lt;/li&gt;
&lt;li&gt;What's missing that would make this part of your daily flow?
&lt;strong&gt;Try it here:&lt;/strong&gt; &lt;a href="https://chat-vault-drab.vercel.app/" rel="noopener noreferrer"&gt;https://chat-vault-drab.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kick the tires, be brutal, tell me everything. And I'm genuinely curious in the comments: &lt;strong&gt;what's the one AI conversation you wish you could pull back up right now and which model would you hand your memory file to first?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Choosing a Vector Database in 2026: pgvector vs. Pinecone vs. Qdrant vs. Weaviate vs. Milvus</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Wed, 24 Jun 2026 17:56:49 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/choosing-a-vector-database-in-2026-pgvector-vs-pinecone-vs-qdrant-vs-weaviate-vs-milvus-422k</link>
      <guid>https://dev.to/arya_koste_5845807df94776/choosing-a-vector-database-in-2026-pgvector-vs-pinecone-vs-qdrant-vs-weaviate-vs-milvus-422k</guid>
      <description>&lt;p&gt;Every RAG tutorial pulls the same move. It walks you through embeddings, chunking, retrieval, and then right at the moment of truth it says "now pick a vector database" and just... moves on. Like that's a coffee-order decision. Like you're choosing a font.&lt;/p&gt;

&lt;p&gt;It's not. The database you pick decides how fast your queries come back, how much you bleed in infrastructure costs, and how miserable your life gets the day traffic triples. And here's the thing that took me a while to figure out while researching this: the database you start with is often not the one you ship. Teams migrate, sometimes more than once. Some of those moves are clean wins; some are weeks of pain and a postmortem doc.&lt;/p&gt;

&lt;p&gt;So let's try to skip the regret. I spent a long time digging through benchmarks, production write-ups, and the trade-offs people only discover after the fact, and this is the guide I wish I'd had when I started: what each of these five (pgvector, Pinecone, Qdrant, Weaviate, and Milvus) is actually good at, where each quietly falls apart, and how to pick without drowning in a feature matrix.&lt;/p&gt;

&lt;p&gt;I'll spoil the ending, because it's the most useful thing here: &lt;strong&gt;pick based on the infrastructure you already run, not the benchmark you just read.&lt;/strong&gt; If you're already on Postgres, the answer is probably pgvector and you can almost stop reading. If you never want to touch infrastructure again, it's Pinecone. Everything past that is fine-tuning.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cheat sheet, for the impatient
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If this is you&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;The one-liner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Already on Postgres, under ~50M vectors&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;pgvector&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One database, zero new problems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want it to just work, will pay for that&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pinecone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hand them a key, walk away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosting and obsessed with speed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Qdrant&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The Rust one. It's fast.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid search is the whole point&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Weaviate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did keyword + vector before it was cool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading toward a billion vectors&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Milvus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built for the scale that breaks everything else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now the actual reasoning.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, stop staring at the benchmarks
&lt;/h2&gt;

&lt;p&gt;I get the pull. The benchmarks are right there, with their satisfying little bar charts, and one database is 3ms faster than another and that &lt;em&gt;feels&lt;/em&gt; like a decision. It mostly isn't. At the scale almost everyone actually operates, every database on this list is fast enough that your users will never feel the difference. The latency war is largely fought over workloads you don't have yet.&lt;/p&gt;

&lt;p&gt;What actually decides this, in roughly this order, is four boring questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do you already run?&lt;/strong&gt; If Postgres is already humming along in your stack (and if you're a full-stack dev, it probably is), bolting on a second database means a sync layer, separate backups, and a whole new thing that can page you at 3am. That gravity is real, and it usually wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you want to run infrastructure at all?&lt;/strong&gt; Some people find ops satisfying. Some people would rather light money on fire than configure another cluster. Both are valid. Be honest about which one you are.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How big does this actually get?&lt;/strong&gt; Under 10 million vectors, genuinely anything works. Between 10 million and a billion, the field thins out fast. Past a billion, you've got maybe two real options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you need hybrid search?&lt;/strong&gt; This one sneaks up on people. Pure semantic search is great until a user types an exact product ID or a version number and your "vibes-based" retrieval shrugs. More on this later, but file it away.&lt;/p&gt;

&lt;p&gt;Answer those four honestly and the database basically picks itself. Here's each one against that lens.&lt;/p&gt;




&lt;h2&gt;
  
  
  pgvector: the answer that's almost boring enough to be right
&lt;/h2&gt;

&lt;p&gt;pgvector isn't a database. It's a Postgres extension. You teach the database you already have how to do vector search, and that's the whole trick. No new service. No sync layer. No 2am pages from a system you provisioned in a hurry and never fully understood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's so often the right call.&lt;/strong&gt; Your vectors sit right next to your actual data, so you can query both in a single transaction, your existing backups already cover them, and filtering is just a SQL &lt;code&gt;WHERE&lt;/code&gt; clause: joins, subqueries, the works. The old "Postgres is too slow for vectors" complaint is genuinely outdated; it comes from the bad-old-days IVFFlat era. Once HNSW indexing landed, pgvector started matching dedicated databases at a million vectors, and with the pgvectorscale extension it holds its own well past that, with competitive throughput at 99% recall on tens of millions of vectors. For a huge share of RAG projects this is the right answer, and its only real crime is being unexciting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it starts to hurt.&lt;/strong&gt; Push past a couple million vectors on one box and index builds start taking twenty-plus minutes, and Postgres's VACUUM process starts elbowing your queries for resources. There's also a subtler trap: pgvector filters &lt;em&gt;after&lt;/em&gt; it searches, so a picky filter on a big collection means scanning a pile of candidates to hand you back a handful. Somewhere around 50-100 million vectors you'll be reaching for read replicas, partitioning, or the exit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it if:&lt;/strong&gt; you're on Postgres, under ~50M vectors, and you don't need to win a latency contest. Most teams are exactly this and don't realize it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pinecone: pay money, make the problem disappear
&lt;/h2&gt;

&lt;p&gt;Pinecone basically invented the managed-vector-database category and still sets the bar for it. You get an API key, you make an index, you throw vectors at it, you query them back. There's nothing to size, nothing to tune, nothing to keep alive. That's not a feature list. That's the entire pitch, and it's a genuinely good one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why people love it.&lt;/strong&gt; "I need a vector database and I refuse to think about it any further" is a completely legitimate position, and Pinecone is built precisely for that person. It scales to billions of vectors, it does hybrid search, it's got the enterprise compliance checkboxes. You offload the whole operational headache to someone whose entire job is keeping it up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch (and there's a real one).&lt;/strong&gt; It's closed-source and there's no self-hosting, full stop. Your data lives in their cloud, and if their pricing shifts or they have a bad day, you have no plan B. The serverless tier swaps latency for convenience and pins your recall at around 90% with no knob to turn. Pinecone just doesn't let you touch the HNSW internals. Writes are eventually consistent, so freshly-added vectors take a beat to show up. Filtering is weaker than SQL (no joins, no subqueries), so anything fancy spills back into your application code. And the bill grows with you, sometimes faster than you'd like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it if:&lt;/strong&gt; not running infrastructure matters more to you than tuning control, and you've made peace with lock-in as the price of never thinking about this again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Qdrant: the fast one, and it knows it
&lt;/h2&gt;

&lt;p&gt;Qdrant is open-source, written in Rust, and built from day one to do vector search and basically nothing else. That focus shows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's appealing.&lt;/strong&gt; It's the speed leader of the open-source pack, with the lowest p50 latency of the bunch, and the gap actually widens under heavy query load where it matters most. But the real party trick is filtered search: Qdrant runs your filters &lt;em&gt;inside&lt;/em&gt; the search traversal instead of bolting them on afterward, so the exact "find me similar shoes, but only the red ones in stock" query that makes pgvector sweat stays snappy here. Add binary quantization that can shrink memory dramatically, plus newer cloud features like GPU-accelerated indexing and multi-AZ failover, and you've got a lot of database for the money. The free tier is the most generous on this list, too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The honest downside.&lt;/strong&gt; The ecosystem is younger. You'll find fewer Stack Overflow threads at midnight, fewer random blog posts that happen to solve your exact problem. Though LangChain and LlamaIndex both support it just fine, so you're not in the wilderness. And some teams have reported friction under really heavy concurrent writes, so if you're write-hammering it, test that specifically before you commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it if:&lt;/strong&gt; you'll happily run your own infrastructure, you want the best performance per dollar, and fast filtered search is central to what you're building.&lt;/p&gt;




&lt;h2&gt;
  
  
  Weaviate: the one that wants to be your whole pipeline
&lt;/h2&gt;

&lt;p&gt;Weaviate is open-source too, but it's playing a different game. It doesn't just want to store your vectors, it wants to be the backbone of your entire AI pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it stands out.&lt;/strong&gt; Two reasons. First, hybrid search, which Weaviate shipped back in late 2022, ages before most of the field caught on. Its implementation is genuinely mature (proper keyword scoring fused with vector results), so if blending semantic and exact-match retrieval is core to your product, this is the most grown-up native option out there. Second, it'll do your embeddings for you: send raw text, and Weaviate vectorizes it on the way in using OpenAI, Cohere, Hugging Face, whatever. Fewer moving parts in your pipeline, which is a real quality-of-life win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it gets heavy.&lt;/strong&gt; Those built-in vectorizers aren't free. They call the same embedding APIs you'd call yourself, so you're paying that latency and cost, just somewhere less visible. The GraphQL API is a genuine learning curve if your brain is wired for SQL or plain REST. And the Java runtime is hungry when you self-host; operational complexity climbs as you pile on modules and multi-tenancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it if:&lt;/strong&gt; native hybrid search is a hard requirement, or you genuinely want the database to handle vectorization so your pipeline has fewer parts to break.&lt;/p&gt;




&lt;h2&gt;
  
  
  Milvus: bring this one out when things get enormous
&lt;/h2&gt;

&lt;p&gt;Milvus is the most-starred open-source option on GitHub, backed by Zilliz (whose managed Zilliz Cloud is the souped-up enterprise version). It exists for one reason: scale that makes everything else fall over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it earns its place.&lt;/strong&gt; Nothing else here touches its distributed architecture when you're genuinely big. Heading past a billion vectors? Need GPU-accelerated indexing? This is the tool built for that exact day. At the 100M+ range it's a serious candidate, full stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why you shouldn't reach for it casually.&lt;/strong&gt; It's a lot of machine. The operational complexity outpaces Qdrant's for normal use cases. Budget real engineering time for the Kubernetes wrangling, and expect your team to climb a learning curve. There's also a known performance gotcha around output fields that can bite in RAG pipelines. If you're under a few tens of millions of vectors, Milvus is almost certainly more database than your problem deserves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it if:&lt;/strong&gt; you're truly heading into 100M-1B+ territory or need distributed GPU indexing, and you've got the ops capacity to run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  A quick word on the ones I skipped
&lt;/h2&gt;

&lt;p&gt;ChromaDB earns an honorable mention. It runs right inside your Python process with zero config and is a joy for prototyping. It just doesn't bring the production tooling (high availability, snapshots, real monitoring) the big five do, so think of it as where you start, not where you stay. Faiss (a library, not a database), LanceDB (multi-modal first), Vespa, and the cloud-native managed options like Vertex Vector round out the wider field, but the five above are the ones people actually argue about in production Slack channels.&lt;/p&gt;




&lt;h2&gt;
  
  
  The hybrid-search thing I keep promising to explain
&lt;/h2&gt;

&lt;p&gt;Here it is, because it quietly decides more choices than people expect. Pure vector search is wonderful right up until someone searches for an exact SKU, a person's name, or "v2.3.1", and suddenly your beautiful semantic embeddings return something thematically adjacent but useless. Real systems need both: the fuzzy semantic match &lt;em&gt;and&lt;/em&gt; the exact keyword hit, in the same query.&lt;/p&gt;

&lt;p&gt;Weaviate and Qdrant ship this natively. Pinecone bolted it on. pgvector makes you assemble it yourself by hand. If you're building agent memory or RAG over genuinely varied content, treat hybrid search as non-negotiable and let it tilt your decision. It's not a nice-to-have anymore; it's table stakes wearing a trench coat pretending to be a differentiator.&lt;/p&gt;




&lt;h2&gt;
  
  
  So, what do you actually pick?
&lt;/h2&gt;

&lt;p&gt;If you take one thing from all of this:&lt;/p&gt;

&lt;p&gt;You're on Postgres and under ~50M vectors? Use &lt;strong&gt;pgvector&lt;/strong&gt; and go build something. Stop researching.&lt;/p&gt;

&lt;p&gt;You never want to see a config file? &lt;strong&gt;Pinecone&lt;/strong&gt;, and make peace with the lock-in.&lt;/p&gt;

&lt;p&gt;Self-hosting and you care about speed or filtering? &lt;strong&gt;Qdrant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hybrid search is the heart of the product? &lt;strong&gt;Weaviate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Staring down hundreds of millions of vectors? &lt;strong&gt;Milvus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the meta-lesson under all of it: the benchmarks are tie-breakers, not deciders. The database you prototype with probably isn't the one you'll ship, so optimize for the one that's easiest to reason about today and least painful to leave tomorrow. For most teams, honestly, that's pgvector right up until something specific drags them off it. And "something specific" tends to arrive a lot later than the breathless blog posts would have you believe.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;One fair warning on the numbers in here: the benchmark figures floating around the industry come from wildly different test setups: different vector dimensions, different hardware, different recall targets, and quite often run by the vendor whose database happens to win. Treat all of it as directionally true rather than gospel, and test against your own data at your own scale before you bet the product on it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>database</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Building a 3D Game in the Browser Humbled Me. Here's Everything That Fought Back.</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Sun, 21 Jun 2026 19:32:38 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/building-a-3d-game-in-the-browser-humbled-me-heres-everything-that-fought-back-4mmg</link>
      <guid>https://dev.to/arya_koste_5845807df94776/building-a-3d-game-in-the-browser-humbled-me-heres-everything-that-fought-back-4mmg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Coordinate systems. Rotated bounding boxes. A sine wave cycling 637 times per second. A tribute to Alan Turing hidden in binary light. This is how Solstice Village came to be.&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;June 21. The longest day. Light and darkness at their most dramatic inflection point.&lt;/p&gt;

&lt;p&gt;I wanted something that &lt;em&gt;felt&lt;/em&gt; like the solstice — not a platformer about suns, not a timer counting daylight seconds. I wanted the quiet tension of a village where every window has gone dark on the longest night, and the warmth that spreads as you, one courier with a lantern, carry light back home to home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solstice Village&lt;/strong&gt; is a 3D exploration game built in Three.js. You play as Sol, walking a circular village, talking to keepers, gathering light at the central brazier, and lighting eight dark homes until the sky shifts from midnight indigo to morning gold.&lt;/p&gt;

&lt;p&gt;Play it here: &lt;a href="https://aryakoste.github.io/solsticeGame/" rel="noopener noreferrer"&gt;https://aryakoste.github.io/solsticeGame/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You're Actually Playing
&lt;/h2&gt;

&lt;p&gt;The village is a ring. At the centre: a brazier that flickers. Around it at radius 17 units: eight homes, each dark. Between them at radius 11.5: lamp posts. Beyond the homes: 32 swaying trees, fireflies, a deer, a cat, an owl blinking on a post, and — if you find it — a secret grove with glowing mushrooms and a standing stone.&lt;/p&gt;

&lt;p&gt;The main game loop is: &lt;strong&gt;Talk → Gather → Deliver → Watch the sky change.&lt;/strong&gt; Each house lit raises &lt;code&gt;dawnAmount&lt;/code&gt; by 1/8. That single float drives everything — sky colour, sun position, fog, ambient light, moon fade. The world literally brightens as you play.&lt;/p&gt;

&lt;p&gt;But there is a second ending. If you collect all six spirit wisps scattered around the village without lighting a single home, the game takes a different turn entirely. The village stays dark. The grove awakens. The stone hums. A different piece of narration plays. It rewards the player who ignores the instructions and wanders instead — which felt right for a solstice game, where the night itself has value.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why 3D on the Web Is a Different Beast Entirely
&lt;/h2&gt;

&lt;p&gt;Most developers who've shipped 2D browser games look at a 3D game and think: &lt;em&gt;it's just one more axis, how much harder can it be?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is: in almost every possible way.&lt;/p&gt;

&lt;p&gt;In 2D, your world is flat. Movement is &lt;code&gt;x += vx&lt;/code&gt;. Collision is rectangle overlap. Camera is a viewport offset. You render sprites front-to-back and call it done. The computer does what your intuition expects.&lt;/p&gt;

&lt;p&gt;In 3D, almost nothing does what your intuition expects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything lives in multiple coordinate spaces at once.&lt;/strong&gt; Your player exists in world space. The camera that watches them exists in view space. Objects like houses have their own local space, rotated relative to the world. When you need to know if the player is colliding with a rotated house, you can't just compare coordinates — you have to transform the player into the house's local space, do the math there, then rotate the result back. Get any step wrong and the player clips through walls or gets ejected into the sky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rotations stop being numbers and become order-dependent operations.&lt;/strong&gt; In 2D, rotating by 30° then 45° gives the same result as rotating by 45° then 30°. In 3D, the order matters. Rotation around X then Y is not the same as Y then X. This is not a quirk — it's fundamental to how 3D rotations work (they aren't commutative). Every character, every NPC, every camera angle in a 3D game is a product of carefully ordered rotations, and one wrong assumption cascades into every object in the scene pointing the wrong direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The camera is its own 3D object, not a window.&lt;/strong&gt; In 2D you scroll a viewport. In 3D, your camera has a position, an orientation, a field of view, a near clip plane, a far clip plane. A third-person camera orbiting a player requires converting spherical coordinates (yaw angle, pitch angle, distance) into Cartesian (x, y, z). That formula is six trigonometry calls per frame, and if any sign is wrong the camera clips underground, or drifts into the player's head, or spins the wrong direction when you drag the mouse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lighting is a full second discipline.&lt;/strong&gt; In 2D you colour pixels. In 3D, light sources emit in all directions, cast shadows that require their own depth-buffer render pass, bounce off surfaces according to material properties (roughness, metalness, normal maps), and interact with ambient light, hemisphere light, and fog — all simultaneously. Getting a scene to &lt;em&gt;feel&lt;/em&gt; right at night, then &lt;em&gt;feel&lt;/em&gt; like dawn, requires understanding all of those systems well enough to tune them together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time is not a given.&lt;/strong&gt; In 2D games you often get away with &lt;code&gt;position += speed&lt;/code&gt; per frame. In 3D you quickly learn that frame rate varies — a phone runs at 30fps, a desktop at 120fps — and if you animate against raw time without delta-time scaling, your game runs fast on powerful hardware and slow on weak hardware. And even delta-time isn't enough: the specific unit of time matters. Miss a milliseconds-to-seconds conversion anywhere and your animation blows up in ways that look completely inexplicable until you work out the arithmetic.&lt;/p&gt;

&lt;p&gt;These aren't beginner mistakes you outgrow. They are the permanent operational cost of building in three dimensions. Every feature you add has to be thought through in all three axes, across multiple coordinate spaces, accounting for arbitrary rotation and variable time. And you discover most of them by shipping a bug.&lt;/p&gt;

&lt;p&gt;Here's what Solstice Village taught me.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Part (Where the 3D Tax Got Collected)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: The NPC That Stared at Walls
&lt;/h3&gt;

&lt;p&gt;Every NPC was placed around the village ring facing &lt;em&gt;away&lt;/em&gt; from the plaza — backs to the player, faces to their own house wall. Maddening.&lt;/p&gt;

&lt;p&gt;The culprit: &lt;code&gt;Three.js r128&lt;/code&gt;'s &lt;code&gt;Object3D.lookAt()&lt;/code&gt;. This is exactly the kind of 3D convention trap that costs hours. &lt;code&gt;lookAt&lt;/code&gt; on any object makes the &lt;strong&gt;local −Z axis&lt;/strong&gt; face the target — the camera convention carried over from OpenGL, where cameras look down -Z by default. When you call &lt;code&gt;grp.lookAt(0,0,0)&lt;/code&gt;, Three.js rotates the object so its &lt;em&gt;back&lt;/em&gt; (-Z) faces the origin. Since the character's face is built at local +Z, every NPC literally showed me their back.&lt;/p&gt;

&lt;p&gt;The fix required abandoning &lt;code&gt;lookAt&lt;/code&gt; entirely and thinking in terms of the angle I actually needed:&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="c1"&gt;// WRONG — makes local -Z face origin (NPC backs to player)&lt;/span&gt;
&lt;span class="nx"&gt;grp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&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="mi"&gt;0&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="c1"&gt;// RIGHT — makes local +Z (the face) point toward origin&lt;/span&gt;
&lt;span class="nx"&gt;grp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;out&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="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;Math.atan2(dy, dx)&lt;/code&gt; gives the angle of a 2D vector. When you negate both components — &lt;code&gt;(-out.x, -out.z)&lt;/code&gt; — you get the angle pointing &lt;em&gt;toward&lt;/em&gt; the origin from the NPC's position. Since Three.js character faces have eyes at local &lt;code&gt;+Z&lt;/code&gt;, this finally made everyone look at the plaza. The same formula drives NPC head-tracking when you walk close:&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="c1"&gt;// Track player — local +Z toward player&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;playerPos&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;-&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;pos&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;playerPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&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;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&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;dz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two negations, two formulas, same underlying geometry — just applied in opposite directions. The symmetry is elegant once you find it. Finding it is the hard part.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 2: The Sine Wave That Cycled 637 Times Per Second
&lt;/h3&gt;

&lt;p&gt;The cat was vibrating. The wisps were strobing. Every frame, the cat's Y position was essentially random — not glitching in some obvious broken way, just wrong, jittering too fast to see what it was trying to do.&lt;/p&gt;

&lt;p&gt;The animation code looked fine:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&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;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is where 3D's time problem came to collect. &lt;code&gt;now&lt;/code&gt; came from &lt;code&gt;performance.now()&lt;/code&gt; — which returns &lt;strong&gt;milliseconds&lt;/strong&gt;. At 60 fps, &lt;code&gt;now&lt;/code&gt; increments by ~16.7 per frame. &lt;code&gt;Math.sin(now * 4)&lt;/code&gt; means &lt;code&gt;Math.sin(16.7 × 4)&lt;/code&gt; = &lt;code&gt;Math.sin(66.8 radians)&lt;/code&gt; per frame. One full sine cycle is &lt;code&gt;2π ≈ 6.28 radians&lt;/code&gt;. So per frame, the animation was completing:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;66.8 ÷ 6.28 ≈ 10.6 full cycles per frame
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At 60 fps, that's &lt;strong&gt;637 cycles per second&lt;/strong&gt;. The position was sampling a different, effectively random point on the sine curve every single frame. The cat wasn't jittering — it was animating perfectly at ultrasonic speed. You just can't see 637 Hz with human eyes.&lt;/p&gt;

&lt;p&gt;Fix: convert milliseconds to seconds before using as a time parameter:&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="c1"&gt;// Cat bob — multiply by 0.004 = (0.001 to convert ms→s) × (4 Hz frequency)&lt;/span&gt;
&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&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;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Wisp float — explicit ms→s conversion&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.001&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;phase&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="nx"&gt;orb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;oy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.28&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The firefly animation, written earlier, had correctly used &lt;code&gt;now * 0.001&lt;/code&gt;. The roamers and wisps were missing it. One factor of 1000. The difference between a cat that bobs at 4 Hz and a cat that vibrates into another dimension.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 3: The Camera Orbit
&lt;/h3&gt;

&lt;p&gt;Third-person camera in 3D needs two angles: &lt;strong&gt;yaw&lt;/strong&gt; (horizontal rotation around the player) and &lt;strong&gt;pitch&lt;/strong&gt; (vertical tilt). Converting those spherical coordinates to a Cartesian camera position is one of those formulas you derive, get wrong, stare at, re-derive:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CAM_DIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;7.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;camYaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// starts behind player, facing −Z toward plaza&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&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;-&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;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camPitch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CAM_DIST&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;cz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camPitch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CAM_DIST&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;cy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.4&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camPitch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;CAM_DIST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&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="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;Math.cos(camPitch)&lt;/code&gt; factor collapses the horizontal offset as pitch increases — when you're looking straight down, the camera is directly above. The &lt;code&gt;Math.sin(camPitch) * CAM_DIST&lt;/code&gt; lifts the camera vertically as you tilt up. Both terms together trace a sphere of radius &lt;code&gt;CAM_DIST&lt;/code&gt; centred on the player.&lt;/p&gt;

&lt;p&gt;Then the movement system has to use that same yaw to map WASD onto world directions, so pressing W always means "forward relative to where the camera is looking":&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;fwd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;strafe&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;wz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;fwd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;camYaw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;strafe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In 2D this is trivial. In 3D, getting the minus signs right between camera orbit and player movement — so that pressing D strafes right relative to the camera, not world-right — takes real attention.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 4: Collision Detection Across Rotated Space
&lt;/h3&gt;

&lt;p&gt;Two types of colliders in the world: &lt;strong&gt;circular&lt;/strong&gt; (trees, lamp posts, well, NPCs) and &lt;strong&gt;oriented box&lt;/strong&gt; (rotated houses). Circular collision is manageable:&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;_resolveCircle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&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;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&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;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dz&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;minDist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;playerRadius&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;minDist&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;player&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;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minDist&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dz&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minDist&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Box colliders for houses are the full 3D problem. Each house is rotated to face the plaza — so the box's axes aren't aligned with the world. You can't just compare world-space coordinates. You have to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Transform the player into the house's local coordinate space&lt;/li&gt;
&lt;li&gt;Find the closest point on the axis-aligned box &lt;em&gt;in local space&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Compute the penetration depth&lt;/li&gt;
&lt;li&gt;Transform the push vector back to world space
&lt;/li&gt;
&lt;/ol&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;_resolveBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;sin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotY&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;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&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;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// step 1: rotate into box local space&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// step 2: nearest point on box surface&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hw&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lx&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;pz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hd&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lz&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;ox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lz&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;pz&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;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&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;ox&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;oz&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;oz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;playerRadius&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="nx"&gt;ox&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;oz&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;d&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;push&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;playerRadius&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// step 3: rotate push back to world space&lt;/span&gt;
    &lt;span class="nx"&gt;player&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;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;nz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Fourteen lines. Two full rotation transforms. This took longer to get right than any other system in the game — and it's the kind of thing you genuinely cannot debug by guessing. You have to understand the coordinate space transform or you'll never know which of the four signs is wrong.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 5: Dawn as a Single Variable
&lt;/h3&gt;

&lt;p&gt;The whole sky — background colour, sun height, moon opacity, fog, ambient light, directional light — is driven by one number: &lt;code&gt;dawnAmount&lt;/code&gt;, a float from 0 (full night) to 1 (full dawn). This is how you avoid a lighting system that becomes unmanageable. One float. Every system reads it.&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="c1"&gt;// Sky background lerp&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;night&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;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x0c1130&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;dawn&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;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x3a4a82&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;night&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dawn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dawnAmount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;smoothing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Sun rises from underground to sky&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sunY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dawnAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;62&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// -12 to +50 units&lt;/span&gt;
&lt;span class="nx"&gt;sunMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sunY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;sunMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sunY&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Moon fades as sun rises&lt;/span&gt;
&lt;span class="nx"&gt;moonMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dawnAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Scene lighting scales with dawn&lt;/span&gt;
&lt;span class="nx"&gt;sunLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dawnAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;hemi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dawnAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The sun colour shifts from orange to yellow at 40% dawn — mimicking the red horizon of early sunrise shifting to the cleaner yellow of morning:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;sunMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dawnAmount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mh"&gt;0xff9940&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0xffe680&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The six wisps scattered around the village each add 8% dawn when collected — a reward for exploration that makes the world respond to curiosity before the main quest requires it. Each wisp also shifts the lantern to its own colour (teal, blue, green) so the light you carry looks different depending on where you've been.&lt;/p&gt;


&lt;h3&gt;
  
  
  Challenge 6: The Lantern as a Resource, Not a Flag
&lt;/h3&gt;

&lt;p&gt;The original design treated "carrying light" as a binary state — you either had it or you didn't. That worked mechanically, but it meant every trip from the brazier to a home was identical. No tension, no urgency.&lt;/p&gt;

&lt;p&gt;The fix was turning the lantern into a fuel system. Light gathered at the brazier fills a &lt;code&gt;lanternFuel&lt;/code&gt; float to 1.0. It drains at roughly 1/85th per second — enough for two or three homes per trip at walking pace. As it drains, the lantern physically dims: intensity and opacity both scale with fuel level. The HUD tracks it with colour: gold when full, orange past the halfway point, red and labelled "almost out!" below 20%.&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="c1"&gt;// Each frame while carrying:&lt;/span&gt;
&lt;span class="nx"&gt;lanternFuel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lanternFuel&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lanLight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;lanternFuel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lanMesh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;lanternFuel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lanternFuel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;_clearLantern&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;SV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The light faded — return to the brazier&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;The brazier itself now reacts to how much of the village you've lit. Its flame scales up ~90% in size by full dawn, and its point light gets 70% brighter. The first time you come back to refill and notice the brazier has grown, it feels like the village is waking up with you.&lt;/p&gt;


&lt;h2&gt;
  
  
  Everything That Made the World Feel Alive
&lt;/h2&gt;

&lt;p&gt;On top of the core systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;28 fireflies&lt;/strong&gt; drifting on parametric Lissajous-style curves: &lt;code&gt;x = ox + sin(t × speed) × rx&lt;/code&gt;, &lt;code&gt;z = oz + cos(t × 1.3) × rz&lt;/code&gt; — the asymmetric frequencies mean they never loop in a straight line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Occasional shooting stars&lt;/strong&gt; that spawn at a random point on the sky sphere, trail five fading ghost spheres, animate with &lt;code&gt;requestAnimationFrame&lt;/code&gt;, and clean themselves from the scene after 1.2 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aurora band&lt;/strong&gt; — 12 semi-transparent planes arranged in a ring at radius 70, each tilted slightly differently for an irregular shimmer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,200 star particles&lt;/strong&gt; in a single &lt;code&gt;BufferGeometry&lt;/code&gt; draw call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle burst system&lt;/strong&gt; — pool of sphere meshes with velocity and gravity, fading by life fraction, spawned on house lighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chimney smoke&lt;/strong&gt; — every lit home emits slow expanding grey spheres from its chimney top every 0.75 seconds, growing as they rise and fading out, making lit homes visible from across the village&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bell on house lighting&lt;/strong&gt; — a deep resonant bell rings (four harmonics: 110 Hz, 220 Hz, 440 Hz, 880 Hz, each with a 3.2-second exponential decay) underneath the chord stab, so lighting a home sounds like an event&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Footstep audio&lt;/strong&gt; — alternating low tones (78 Hz / 68 Hz) timed to walking pace via a step timer, faster when running. All Web Audio API, no sound files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web Audio API ambient drone&lt;/strong&gt; — five overlapping sine oscillators at 55 Hz, 82.4 Hz, 110 Hz, 164.8 Hz, and 220 Hz (a harmonic series), each with decreasing gain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Touch joystick&lt;/strong&gt; — thumb delta clamped to 46px radius, normalised to ±1, rotated through camera yaw before being applied to movement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimap&lt;/strong&gt; — Canvas 2D drawn every frame: houses as 8×8px squares (dark until lit, gold with glow when lit), NPC positions as dots, player as a filled triangle rotated to match camera yaw&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;House interiors&lt;/strong&gt; — enter any lit home: a full room with ceiling beams, a hearth with animated flame cones, warm flickering point light, a table, a bookshelf, and a rug. Each keeper has a note left on the table — Ash's has a bread recipe, Tovar's has a watchman's log, Mira's has three letters of SOLSTICE written in binary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPC cross-references&lt;/strong&gt; — each keeper mentions another by name in their opening line, so the village feels like people who know each other rather than eight isolated quest-givers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPC dawn gathering&lt;/strong&gt; — past 50% dawn, all eight keepers slowly walk toward the plaza and face the brazier. At full dawn they're clustered together in a silent celebration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cat follows you&lt;/strong&gt; — approach the cat and press E; it trails behind you for the rest of the game. The deer does the opposite: get within five units and it startles, flags a flee direction, and runs at four times its normal speed until you're ten units away&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standing stone reacts to progress&lt;/strong&gt; — at zero homes lit the stone is cold. Past four it glows. At all eight, the Turing inscription appears&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 unique NPCs&lt;/strong&gt; with different skin tones, hair styles, and dialogue arcs that change after their home is lit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tree sway&lt;/strong&gt; — rotation.z and rotation.x oscillating at slightly different frequencies per tree so they never move in sync&lt;/li&gt;
&lt;/ul&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/Aryakoste" rel="noopener noreferrer"&gt;
        Aryakoste
      &lt;/a&gt; / &lt;a href="https://github.com/Aryakoste/solsticeGame" rel="noopener noreferrer"&gt;
        solsticeGame
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Solstice Village&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A 3D exploration game built for the June Solstice. You are Sol, the courier. The longest night has fallen and every window in the village has gone dark. Gather light at the central brazier and carry it home to home until the sky turns gold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://aryakoste.github.io/solsticeGame/" rel="nofollow noopener noreferrer"&gt;Play it here →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Talk to each of the 8 keepers to learn their story&lt;/li&gt;
&lt;li&gt;Gather light at the plaza brazier — your lantern holds ~85 seconds of flame&lt;/li&gt;
&lt;li&gt;Carry the light to dark homes and deliver it&lt;/li&gt;
&lt;li&gt;Watch &lt;code&gt;dawnAmount&lt;/code&gt; rise from 0 to 1 as the village lights up — every system in the game (sky colour, sun height, fog, moon fade, brazier size, NPC behaviour) is driven by this single float&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a second ending. Collect all six spirit wisps scattered around the village without lighting any homes, and the game takes a different path.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Controls&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;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Input&lt;/th&gt;

&lt;th&gt;Action&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;code&gt;W&lt;/code&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/Aryakoste/solsticeGame" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Alan Turing Prize Category
&lt;/h2&gt;

&lt;p&gt;One of the NPCs, Mira the clockmaker, says this when you approach her dark house:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Every lit window is a 1, every dark one a 0. Light mine and we add a bit to the dawn."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's not just flavour text. At the top of the screen runs a &lt;strong&gt;binary strip&lt;/strong&gt; that displays the ASCII value of each letter in "SOLSTICE" — one letter per house you light. When Bell's house lights up, the strip fills in &lt;code&gt;01010011&lt;/code&gt; (S = 83). When Fen's lights up, it fills &lt;code&gt;01001111&lt;/code&gt; (O = 79). Eight houses. Eight letters. Eight bytes spelling out the word in light.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S = 01010011
O = 01001111
L = 01001100
S = 01010011
T = 01010100
I = 01001001
C = 01000011
E = 01000101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the secret grove at the northeast edge of the map, a standing stone reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Beneath the runes, a name: TURING. And below it — ones and zeros."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Binary as metaphor for light. Light as computation. Every dark window a zero waiting to be flipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google AI in the Process
&lt;/h2&gt;

&lt;p&gt;Google AI Studio and Gemini were part of the development loop throughout — helping reason through Three.js's coordinate conventions, sanity-checking the spherical camera orbit math, and catching the milliseconds-to-seconds unit mismatch before it became a harder bug. When I described the NPC-facing problem, Gemini confirmed the camera-convention issue with &lt;code&gt;lookAt&lt;/code&gt; and helped verify the &lt;code&gt;atan2&lt;/code&gt; formula direction before I changed code. The kind of quick second opinion that saves an hour of trial and error at midnight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Play It
&lt;/h2&gt;

&lt;p&gt;Works in any modern browser, desktop and mobile.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WASD&lt;/code&gt; / left joystick — move&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Shift&lt;/code&gt; — run&lt;/li&gt;
&lt;li&gt;Click + drag, or touch the right side of the screen — rotate camera&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;E&lt;/code&gt; / Talk button — interact with NPCs, brazier, homes, wisps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find the secret grove in the northeast. Read the standing stone — it says something different depending on how many homes you've lit. Pet the cat. Let the deer startle away from you. Light all eight homes and watch the sky turn.&lt;/p&gt;

&lt;p&gt;Or don't light any of them. Collect all six wisps instead, and see what the night has to say.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The longest night ends. That's what the solstice is: not the absence of light, but the moment right before it comes back.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;#gamedev&lt;/code&gt; &lt;code&gt;#javascript&lt;/code&gt; &lt;code&gt;#threejs&lt;/code&gt; &lt;code&gt;#webdev&lt;/code&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>ClawCraft: Bringing a Full Visual Studio to OpenClaw 🛠️</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Sat, 25 Apr 2026 22:21:00 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/clawcraft-bringing-a-full-visual-studio-to-openclaw-1iem</link>
      <guid>https://dev.to/arya_koste_5845807df94776/clawcraft-bringing-a-full-visual-studio-to-openclaw-1iem</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Building OpenClaw skills is an incredibly powerful way to automate workflows across messaging platforms. But let's be honest: managing them locally, writing YAML/JSON boilerplate, ensuring they are safe to run, and debugging failures without a proper environment can require more context-switching than we'd like.&lt;/p&gt;

&lt;p&gt;So, for this developer challenge, I set out to build &lt;strong&gt;ClawCraft&lt;/strong&gt;, a comprehensive local development environment and companion skill specifically designed for OpenClaw. &lt;/p&gt;

&lt;p&gt;ClawCraft isn't just a basic tool—it's a &lt;strong&gt;powerful Command Line Interface (CLI)&lt;/strong&gt;, a &lt;strong&gt;local web dashboard (Client UI)&lt;/strong&gt; running a Fastify backend server, and an &lt;strong&gt;OpenClaw Skill&lt;/strong&gt;. It allows you to build, scan, debug, and manage your skills without ever touching code—&lt;em&gt;unless you want to.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Aryakoste/ClawCraft" rel="noopener noreferrer"&gt;&lt;strong&gt;⭐ Check out the GitHub Repository here! ⭐&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 The Client UI: A Beautiful Local Dashboard
&lt;/h2&gt;

&lt;p&gt;First and foremost, ClawCraft launches a sleek, React-based web dashboard locally on your machine. This is your command center. &lt;/p&gt;

&lt;p&gt;Instead of messing around with local folders, strict syntax, and terminal logs, the Client UI gives you a visual overview of every OpenClaw skill you have installed, the ability to trigger API commands instantly, and access to all the core developer tools right from your browser. &lt;/p&gt;




&lt;h2&gt;
  
  
  🔥 Headline Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 🏗️ Visual Skill Builder
&lt;/h3&gt;

&lt;p&gt;Why write boilerplate when you can just drag and drop? The intuitive Client UI uses &lt;code&gt;@dnd-kit&lt;/code&gt; to let you string together the logic of your OpenClaw skills visually. For power users who still want granular control over their code, there's a fully integrated Monaco code editor providing a rich, IDE-like fallback experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 🤖 AI Skill Generation
&lt;/h3&gt;

&lt;p&gt;Have an idea but don't want to drag blocks or write code? Tell ClawCraft what you want! Powered by Anthropic and OpenAI SDKs directly in the backend, you can just type a natural language prompt (e.g., &lt;em&gt;"Make a skill to fetch top 3 hacker news articles"&lt;/em&gt;). The AI will scaffold the entire OpenClaw skill structure, parameters, and logic for you instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🚨 Built-in Security Scanner with Trust Scores
&lt;/h3&gt;

&lt;p&gt;You shouldn't just run random skills you find online blindly on your system. ClawCraft includes an automatic security scanner that analyzes any external skill (via URL or name), scanning the implementation logic, and summarizing exactly &lt;em&gt;what&lt;/em&gt; it is attempting to do. &lt;br&gt;
The scanner returns a &lt;strong&gt;Trust Score&lt;/strong&gt; directly in the UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  ✅ &lt;strong&gt;Safe&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;  ⚠️ &lt;strong&gt;Review Required&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;  🚨 &lt;strong&gt;Dangerous&lt;/strong&gt; (Do not install!) &lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  4. 🪲 Live Debugger
&lt;/h3&gt;

&lt;p&gt;Testing an OpenClaw skill but it's failing silently in Slack or Telegram? Launch the local debugger dashboard. ClawCraft lets you inspect variables, view the actual HTTP payloads, and trace exactly where the flow broke in real-time.&lt;/p&gt;


&lt;h2&gt;
  
  
  💻 The Command Line Interface (CLI)
&lt;/h2&gt;

&lt;p&gt;Prefer living in the terminal? You can control your entire OpenClaw setup using the native CLI. Under the hood, this uses &lt;code&gt;commander.js&lt;/code&gt; and provides beautifully formatted output. &lt;/p&gt;

&lt;p&gt;Here are the commands you have at your disposal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;clawcraft start [--port] [--no-open]&lt;/code&gt; - Starts the local Fastify backend and launches the React dashboard.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft list&lt;/code&gt; - Lists all currently installed skills along with their security trust score badges.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft generate &amp;lt;description&amp;gt;&lt;/code&gt; - Uses LLM integrations to immediately scaffold and format an entire SKILL.md file from a single sentence request.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft scan [target]&lt;/code&gt; - Scans all local skills, or a specific skill by URL or name, for malicious patterns and returns the Trust Score and findings list.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft install &amp;lt;url&amp;gt; [--force]&lt;/code&gt; - Scans an external repository and installs it securely. Prompts if it's considered dangerous unless &lt;code&gt;--force&lt;/code&gt; is used!&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft fix &amp;lt;skillName&amp;gt;&lt;/code&gt; - Immediately opens the browser UI directly to the local debugger for the specified skill.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft improve &amp;lt;skillName&amp;gt;&lt;/code&gt; - An automated AI flow that attempts to rewrite your skill with better guardrails, optimized permissions, and deterministic instructions, and prompts to save!&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;clawcraft config [get/set]&lt;/code&gt; - Read and modify the configuration such as adding API keys safely.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🤯 The Meta Feature: The ClawCraft OpenClaw Skill
&lt;/h2&gt;

&lt;p&gt;Yes, ClawCraft comes with its own native, companion OpenClaw skill (&lt;code&gt;clawcraft&lt;/code&gt; tag) within the repo! &lt;/p&gt;

&lt;p&gt;Once your local ClawCraft server is running, you can manage &lt;em&gt;your entire OpenClaw development environment&lt;/em&gt; directly from Slack, Discord, Whatsapp, or Telegram using slash commands. It's wildly satisfying to build OpenClaw skills &lt;em&gt;using&lt;/em&gt; an OpenClaw skill.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;/clawcraft build "get current bitcoin price"&lt;/code&gt; - Automatically opens your UI and uses AI to generate the skill for you! &lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/clawcraft scan {skill-url}&lt;/code&gt; - Scans the repository for the Trust Score and reads it back to you right in the chat.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/clawcraft list&lt;/code&gt; - Returns a list of all your installed skills.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/clawcraft search "find templates"&lt;/code&gt; - Searches available community templates and installed skills.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/clawcraft status&lt;/code&gt; - See how many times your skills have fired and the estimated time they've saved you!&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🛠️ Tech Stack &amp;amp; Architecture
&lt;/h2&gt;

&lt;p&gt;ClawCraft is structured as a modern JavaScript monorepo using npm workspaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Frontend (&lt;code&gt;packages/client&lt;/code&gt;)&lt;/strong&gt;: React 18, Vite, TailwindCSS for styling, &lt;code&gt;@dnd-kit&lt;/code&gt; for drag-and-drop visuals, and Monaco Editor. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Backend (&lt;code&gt;packages/server&lt;/code&gt;)&lt;/strong&gt;: A lightning-fast Fastify server providing the core OpenClaw system APIs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;CLI Toolkit&lt;/strong&gt;: Powered by Commander.js and Chalk.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI integrations&lt;/strong&gt;: Native SDK integrations for both Anthropic and OpenAI logic generation.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🚀 Quick Start (Running from Source)
&lt;/h2&gt;

&lt;p&gt;ClawCraft is designed to be cloned and run directly from the source to give you absolute control over your local environment. Here is how you can spin everything up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone the repository&lt;/span&gt;
git clone https://github.com/Aryakoste/ClawCraft.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ClawCraft

&lt;span class="c"&gt;# 2. Install dependencies (This securely installs both client and server via npm workspaces)&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 3. Start the application&lt;/span&gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Running &lt;code&gt;npm run dev&lt;/code&gt; kicks off a script that concurrently boot up both the Fastify server on port 4000 and the Vite Client UI dashboard simultaneously!)&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Some Screenshots for UI
&lt;/h1&gt;

&lt;p&gt;Skill Generator&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%2Ft1a9smzz7muzvoc4hsx0.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%2Ft1a9smzz7muzvoc4hsx0.png" alt=" " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Template Gallery&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%2Few9muduvsiawguzbu1do.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%2Few9muduvsiawguzbu1do.png" alt=" " width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skill Debugger&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%2F3l2up1r5cdf2algczz42.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%2F3l2up1r5cdf2algczz42.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skill Composer&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%2Feyktbuai5nr61lps9cwx.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%2Feyktbuai5nr61lps9cwx.png" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usage Tracker &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%2Flmbyk3mnm4uskg05fheo.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%2Flmbyk3mnm4uskg05fheo.png" alt=" " width="800" height="201"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Configuring the AI
&lt;/h2&gt;

&lt;p&gt;ClawCraft's powerful generator (&lt;code&gt;clawcraft generate&lt;/code&gt; and &lt;code&gt;clawcraft improve&lt;/code&gt;) relies on an LLM to do the heavy lifting. You can configure it to use Anthropic, OpenAI, local Ollama endpoints, and more. &lt;/p&gt;

&lt;p&gt;Use the built-in CLI to easily securely set your keys and providers (saved locally to &lt;code&gt;~/.clawcraft/config.json&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set your core provider (anthropic, openai, ollama, openai-compat, openclaw)&lt;/span&gt;
clawcraft config &lt;span class="nb"&gt;set &lt;/span&gt;llmProvider anthropic

&lt;span class="c"&gt;# Provide your authentication key&lt;/span&gt;
clawcraft config &lt;span class="nb"&gt;set &lt;/span&gt;apiKey &lt;span class="s2"&gt;"sk-ant-xxxxxxxxxx"&lt;/span&gt;

&lt;span class="c"&gt;# Set your preferred model version (Defaults to sonnet!)&lt;/span&gt;
clawcraft config &lt;span class="nb"&gt;set &lt;/span&gt;model &lt;span class="s2"&gt;"claude-3-5-sonnet-20241022"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, point your messaging app to the local dashboard (&lt;code&gt;http://localhost:4000&lt;/code&gt;), load up the &lt;code&gt;skill/&lt;/code&gt; directory, and start giving it slash commands! &lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts 💭
&lt;/h2&gt;

&lt;p&gt;I loved exploring how endlessly hackable OpenClaw is during this challenge. My primary goal with ClawCraft was to significantly lower the barrier to entry for creating, auditing, and installing those skills. I firmly believe that making OpenClaw visual, secure, and easily manageable will help onboard a lot more non-developers into the AI automation space. &lt;/p&gt;

&lt;p&gt;Would love any thoughts, PRs, or feedback from the community! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Aryakoste/ClawCraft" rel="noopener noreferrer"&gt;Drop a ⭐ on the Repo or try it out!&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
    </item>
    <item>
      <title>I Put Carbon Pledges on the Solana Blockchain for Earth Day (And Built the AI to Back It Up)</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Sun, 19 Apr 2026 20:00:48 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-put-carbon-pledges-on-the-solana-blockchain-for-earth-day-and-built-the-ai-to-back-it-up-2hin</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-put-carbon-pledges-on-the-solana-blockchain-for-earth-day-and-built-the-ai-to-back-it-up-2hin</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-04-16"&gt;Weekend Challenge: Earth Day Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Did you know that a single cheeseburger emits roughly &lt;strong&gt;3.5 kg of CO₂&lt;/strong&gt;? That's equivalent to driving 14 km in a petrol car — from a &lt;em&gt;sandwich&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Most of us have no idea what our daily choices actually cost the planet. And even when we do, knowing isn't enough. What if you could &lt;em&gt;prove&lt;/em&gt; you're changing — publicly, permanently, on the blockchain?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EcoLens&lt;/strong&gt; is an AI-powered carbon footprint analyzer that goes beyond awareness. It identifies your impact, proposes greener alternatives, and lets you &lt;strong&gt;pledge your commitment on the Solana blockchain&lt;/strong&gt; — a public, immutable record that your word is real.&lt;/p&gt;




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

&lt;p&gt;Carbon footprint calculators exist, but they're tedious. You fill out a form, pick from dropdowns, hit calculate, and get a vague yearly number that means nothing to your Tuesday lunch decision.&lt;/p&gt;

&lt;p&gt;The real gap isn't information — it's &lt;em&gt;accountability&lt;/em&gt;. People know beef is carbon-heavy. They still order it. What changes behavior is a commitment with skin in the game.&lt;/p&gt;

&lt;p&gt;That's the loop I wanted to close: &lt;strong&gt;identify → understand → pledge → act&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What EcoLens Does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📸 Vision Analysis
&lt;/h3&gt;

&lt;p&gt;Upload any image — your meal, a shopping receipt, your car, a product — and &lt;strong&gt;Gemini 2.0 Flash&lt;/strong&gt; analyzes it using real lifecycle emissions data. You don't describe anything; just point your camera.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✍️ Text &amp;amp; Voice Analysis
&lt;/h3&gt;

&lt;p&gt;Type or &lt;em&gt;speak&lt;/em&gt; a description:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"I flew economy class from Mumbai to London"&lt;/em&gt;&lt;br&gt;
→ &lt;strong&gt;1,850 kg CO₂&lt;/strong&gt; · Very High Impact · Equivalent to driving 7,400 km&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ⚖️ Compare Mode
&lt;/h3&gt;

&lt;p&gt;Can't decide? Compare two options side by side — "beef burger vs. black bean burger" — and see exactly how many kg CO₂ you save by choosing the greener option, with a winner crown.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌿 Structured Results
&lt;/h3&gt;

&lt;p&gt;Every analysis returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CO₂ in kg, severity rating, animated breakdown by source&lt;/li&gt;
&lt;li&gt;3 specific, actionable alternatives with estimated savings&lt;/li&gt;
&lt;li&gt;A relatable real-world equivalent&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ◎ Solana On-Chain Pledges ← &lt;em&gt;the key differentiator&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;After every analysis, you can sign a pledge with your Phantom wallet. The commitment is written as a Memo transaction on Solana — permanent, public, tied to your wallet address.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Streaming AI Agent
&lt;/h3&gt;

&lt;p&gt;A persistent chat powered by Gemini that streams responses token-by-token, secured behind Auth0.&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Carbon Budget + History
&lt;/h3&gt;

&lt;p&gt;Set a weekly CO₂ budget. Every analysis is saved to localStorage, tracked against your goal, and visualised in a circular progress meter.&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&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;Framework&lt;/td&gt;
&lt;td&gt;Next.js 15 (App Router)&lt;/td&gt;
&lt;td&gt;Server components + API routes in one repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini 2.0 Flash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vision + text + streaming, accurate emissions data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Auth0 for Agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Protects AI endpoints, user identity scoping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blockchain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Solana (Devnet)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On-chain pledge via Phantom + Memo Program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Animation&lt;/td&gt;
&lt;td&gt;Framer Motion&lt;/td&gt;
&lt;td&gt;Smooth result reveals, animated CO₂ bars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS&lt;/td&gt;
&lt;td&gt;Custom dark earth-tone design system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How I Used Google Gemini
&lt;/h2&gt;

&lt;p&gt;Gemini is the brain of EcoLens — it handles both image analysis and chat via &lt;code&gt;gemini-2.0-flash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The key insight was engineering a &lt;em&gt;structured output contract&lt;/em&gt;. The model always returns valid JSON so the UI can render rich cards without parsing gymnastics:&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;ANALYSIS_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`You are EcoLens AI, an expert environmental scientist.
Analyze the input and return ONLY valid JSON:
{
  "carbonKg": number,
  "category": "food" | "transport" | "energy" | "shopping" | "waste" | "other",
  "title": string,
  "summary": string,
  "breakdown": [{"label": string, "kg": number, "percentage": number}],
  "alternatives": [{"action": string, "impact": string, "reduction": string}],
  "equivalent": string,
  "severity": "low" | "medium" | "high" | "very_high",
  "rating": number
}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For image analysis, the image and prompt go in a single multimodal API call:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Analyze the carbon footprint of everything in this image.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;generationConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// low temp = consistent scientific estimates&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The AI chat uses &lt;strong&gt;streaming&lt;/strong&gt; via &lt;code&gt;sendMessageStream&lt;/code&gt; — responses type out token-by-token with a blinking cursor, making it feel alive:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;streamChatWithEcoAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessageStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The API route pipes this directly into a &lt;code&gt;ReadableStream&lt;/code&gt; response — no buffering, no delay.&lt;/p&gt;


&lt;h2&gt;
  
  
  How I Used Auth0 for Agents
&lt;/h2&gt;

&lt;p&gt;Every call that touches the Gemini AI is &lt;strong&gt;gated by Auth0&lt;/strong&gt; before the agent acts. This is the "Auth0 for Agents" pattern: the AI doesn't act anonymously, it acts &lt;em&gt;on behalf of&lt;/em&gt; a verified identity.&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;// app/api/analyze/route.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authentication required&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Agent acts on behalf of session.user.sub — traceable, auditable&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;analyzeCarbon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;imageMimeType&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;analyzedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&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 same gate protects &lt;code&gt;/api/chat&lt;/code&gt; and &lt;code&gt;/api/compare&lt;/code&gt;. No anonymous AI calls — every analysis is tied to a real, verified person.&lt;/p&gt;


&lt;h2&gt;
  
  
  How I Used Solana — The Heart of the Accountability Loop
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most excited about, and the piece that makes EcoLens more than just another carbon calculator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The idea:&lt;/strong&gt; awareness without commitment changes nothing. Solana lets us make commitments &lt;em&gt;permanent and public&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Gemini analyzes your activity and suggests a greener alternative&lt;/li&gt;
&lt;li&gt;You click &lt;strong&gt;"Sign pledge with Phantom"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;EcoLens constructs a Memo transaction encoding your pledge, the CO₂ saved, and a timestamp&lt;/li&gt;
&lt;li&gt;Phantom signs and broadcasts it to Solana Devnet&lt;/li&gt;
&lt;li&gt;The transaction is confirmed on-chain — your pledge is now permanent
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TransactionInstruction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clusterApiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/web3.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MEMO_PROGRAM_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memoData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EcoLens&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pledge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I pledge to: cycle instead of drive this week&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;carbonKg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TransactionInstruction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isSigner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isWritable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="na"&gt;programId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MEMO_PROGRAM_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&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="nx"&gt;memoData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&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;signature&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;solana&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signAndSendTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;confirmTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastValidBlockHeight&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The pledge is verifiable on &lt;a href="https://explorer.solana.com/?cluster=devnet" rel="noopener noreferrer"&gt;Solana Explorer&lt;/a&gt;. Anyone can look up your wallet and see your carbon commitments, timestamped and immutable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why the Memo Program?
&lt;/h3&gt;

&lt;p&gt;The Solana Memo Program (&lt;code&gt;MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr&lt;/code&gt;) is the simplest, cheapest way to write arbitrary data on-chain. A pledge transaction costs a fraction of a cent in SOL and is confirmed in ~400ms. It's perfect for this use case — lightweight, auditable, and human-readable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why This Matters Beyond the Demo
&lt;/h3&gt;

&lt;p&gt;Carbon pledges have a trust problem. Corporate net-zero commitments get announced and quietly abandoned. There's no receipt. Solana changes that: your pledge is a transaction hash. It can't be edited, deleted, or denied. Future versions of EcoLens could build a public leaderboard of wallets with the most on-chain green commitments — turning individual accountability into social proof.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Real Example: From Analysis to On-Chain Pledge
&lt;/h2&gt;

&lt;p&gt;Here's the full EcoLens loop in action:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User uploads photo of beef steak dinner
   → Gemini Vision: 6.8 kg CO₂ · High Impact

2. Breakdown:
   Beef (production)    ████████  5.2 kg (76%)
   Cooking energy       ██        0.9 kg (13%)
   Transport/packaging  ▌         0.7 kg (11%)

3. Equivalent to: Driving 27 km in a petrol car

4. Top alternative: "Choose chicken instead — Save ~4.1 kg CO₂"

5. User clicks "Sign pledge with Phantom"
   → Phantom signs memo transaction
   → Confirmed on Solana Devnet in 412ms
   → TX: 3xK7...mNpQ (verifiable on Explorer)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the full loop — from a photo to an on-chain commitment in under 30 seconds.&lt;/p&gt;


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

&lt;p&gt;&lt;strong&gt;Structured outputs unlock rich UIs.&lt;/strong&gt; Getting Gemini to reliably return JSON meant the frontend could be fully reactive — no parsing edge cases, no streaming text parsing hacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temperature matters for science.&lt;/strong&gt; At &lt;code&gt;0.7&lt;/code&gt;, CO₂ estimates drift between calls. At &lt;code&gt;0.3&lt;/code&gt;, they're reproducible and match published lifecycle data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solana's Memo Program is criminally underused.&lt;/strong&gt; It's the simplest on-chain data primitive — 0.000005 SOL per transaction, confirmed in milliseconds, human-readable on Explorer. For any app that needs verifiable records, it's a better solution than most people realize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth-gating AI is a pattern, not an afterthought.&lt;/strong&gt; Making the AI agent require Auth0 session means every analysis is tied to a real identity — which is the foundation for future per-user carbon portfolios and wallet-linked pledge histories.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try It Yourself
&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/Aryakoste" rel="noopener noreferrer"&gt;
        Aryakoste
      &lt;/a&gt; / &lt;a href="https://github.com/Aryakoste/ecolens" rel="noopener noreferrer"&gt;
        ecolens
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;🌍 EcoLens&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI Carbon Footprint Analyzer with On-Chain Pledges&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://nextjs.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8e285d6c04022c2a7b4d9c10ab4261dae0236525c30a21caddeb90740d2294bb/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6578742e6a732d31352d626c61636b3f6c6f676f3d6e6578742e6a73" alt="Next.js"&gt;&lt;/a&gt;
&lt;a href="https://ai.google.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3fb5c70ab6db43703e7a1f98d1555981d1166fcf47ed261d8bdc01d32af52a76/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f6f676c655f47656d696e692d322e305f466c6173682d3432383546343f6c6f676f3d676f6f676c65" alt="Gemini"&gt;&lt;/a&gt;
&lt;a href="https://auth0.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3bd7a062d6075189dde3785510450d819b81428b3866dfbc2fb4ce3bcc0aede6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f41757468302d666f725f4167656e74732d4542353432343f6c6f676f3d6175746830" alt="Auth0"&gt;&lt;/a&gt;
&lt;a href="https://solana.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/725e797738a16ebc201de5bd28fc1a4d540b7f075619ff43324cc41cba544fc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f536f6c616e612d4465766e65742d3939343546463f6c6f676f3d736f6c616e61" alt="Solana"&gt;&lt;/a&gt;
&lt;a href="https://typescriptlang.org" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dcff4370c2726b105ada0a3b8a05b83517edd4f9e79215ce2172214b26bc4a5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d352d3331373843363f6c6f676f3d74797065736372697074" alt="TypeScript"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Snap a photo of your lunch, describe your commute, or upload a receipt — EcoLens uses &lt;strong&gt;Google Gemini Vision&lt;/strong&gt; to instantly reveal the hidden CO₂ cost of your daily choices. Then &lt;strong&gt;pledge to do better on the Solana blockchain&lt;/strong&gt; — a permanent, public, verifiable commitment.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&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;Vision Analysis&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Upload any photo — Gemini Vision identifies items and calculates CO₂&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✍️ &lt;strong&gt;Text Analysis&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Describe any activity and get an accurate, sourced emission estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎙️ &lt;strong&gt;Voice Input&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Speak your description using the browser's Speech API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚖️ &lt;strong&gt;Compare Mode&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Race two options side by side — see the greener choice with a winner crown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;◎ &lt;strong&gt;Solana Pledges&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Sign an on-chain pledge with Phantom — permanent, immutable, verifiable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🤖 &lt;strong&gt;Streaming AI Chat&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Gemini-powered eco-agent that streams responses token-by-token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📊 &lt;strong&gt;Carbon Budget&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Set a weekly CO₂ goal and track it against a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Aryakoste/ecolens" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aryakoste/ecolens
&lt;span class="nb"&gt;cd &lt;/span&gt;ecolens
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.local.example .env.local
&lt;span class="c"&gt;# Add GEMINI_API_KEY, Auth0 credentials&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test the Solana pledge feature, install &lt;a href="https://phantom.app" rel="noopener noreferrer"&gt;Phantom wallet&lt;/a&gt; and switch it to &lt;strong&gt;Devnet&lt;/strong&gt; in settings.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Mainnet pledges&lt;/strong&gt; — real SOL-backed carbon commitments with token rewards for follow-through&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Public pledge leaderboard&lt;/strong&gt; — on-chain wallet rankings for green commitments&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;NFT certificates&lt;/strong&gt; — mint a Solana NFT when you hit a weekly carbon goal&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Snowflake analytics&lt;/strong&gt; — aggregate anonymized emissions data across all users for community insights&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prize&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;th&gt;How it's used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best use of Solana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@solana/web3.js&lt;/code&gt; + Phantom&lt;/td&gt;
&lt;td&gt;On-chain carbon pledges via the Memo Program&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best use of Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.0 Flash&lt;/td&gt;
&lt;td&gt;Vision analysis, structured JSON output, streaming chat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best use of Auth0 for Agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@auth0/nextjs-auth0&lt;/code&gt; v3&lt;/td&gt;
&lt;td&gt;Auth-gates every AI endpoint — the agent acts on behalf of a verified identity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;Every gram of CO₂ you avoid is a gift to the planet. And now, you can prove it. Happy Earth Day 🌍&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with Google Gemini 2.0 Flash, Auth0 for Agents, and Solana for the DEV.to Earth Day Challenge 2025.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>solana</category>
    </item>
    <item>
      <title>I Built a Government-Grade Button Clicking Certification Program (Because the World Needs It)</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Tue, 07 Apr 2026 16:47:36 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-built-a-government-grade-button-clicking-certification-program-because-the-world-needs-it-3j82</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-built-a-government-grade-button-clicking-certification-program-because-the-world-needs-it-3j82</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aprilfools-2026"&gt;DEV April Fools Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ NATIONAL CLICK THREAT LEVEL: ELEVATED
&lt;/h2&gt;

&lt;p&gt;Let me ask you something.&lt;/p&gt;

&lt;p&gt;You use buttons every day. Login buttons. Submit buttons. "Accept all cookies" buttons (without reading a single word). Elevator buttons. Crosswalk buttons that don't actually do anything.&lt;/p&gt;

&lt;p&gt;But have you ever been &lt;em&gt;certified&lt;/em&gt; to click those buttons?&lt;/p&gt;

&lt;p&gt;No. You haven't. And frankly, it's reckless.&lt;/p&gt;

&lt;p&gt;That's why I built the &lt;strong&gt;International Bureau of Button Clicking Certification (IBCC)&lt;/strong&gt; — a fully interactive, deeply useless, government-bureaucracy-themed web application that puts you through six increasingly absurd examinations to determine if you are, in fact, qualified to press a rectangular area on an illuminated screen.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏛️ Why Button Clicking Certification Matters in 2026
&lt;/h2&gt;

&lt;p&gt;Let's look at the facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The average knowledge worker clicks &lt;strong&gt;4,217 times per day&lt;/strong&gt;. That's over &lt;strong&gt;1.5 million clicks per year&lt;/strong&gt; — completely unregulated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero countries&lt;/strong&gt; currently require a clicking license. Zero. Not even Switzerland, and they regulate &lt;em&gt;everything&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;In 1987, a single misclick at the Pentagon accidentally ordered &lt;strong&gt;40,000 rubber ducks&lt;/strong&gt;. This is not in any official record because they covered it up. But I believe it happened.&lt;/li&gt;
&lt;li&gt;Self-driving cars are regulated. Surgeons are licensed. Barbers need a certificate. But the person clicking "Deploy to Production" on a Friday at 4:59 PM? No credentials required. &lt;em&gt;Think about that.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The click economy is booming. AI is automating everything &lt;em&gt;except&lt;/em&gt; clicking. If anything, we need &lt;strong&gt;more&lt;/strong&gt; human clickers than ever. And yet, the industry has no standards, no oversight, and no governing body.&lt;/p&gt;

&lt;p&gt;Until now.&lt;/p&gt;

&lt;p&gt;The IBCC was established in 1847 (citation needed) and has been monitoring global click activity from a basement somewhere ever since.&lt;/p&gt;




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

&lt;p&gt;A single-page HTML/CSS/JS application that is equal parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DMV waiting room&lt;/strong&gt; (the aesthetic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escape room&lt;/strong&gt; (the challenges)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fever dream&lt;/strong&gt; (the experience)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Core Experience
&lt;/h3&gt;

&lt;p&gt;You land on the page and you're greeted by an official government seal, a scrolling threat-level ticker, and a giant 3D red button that is begging to be pressed.&lt;/p&gt;

&lt;p&gt;Every click is tracked. Every click is logged. Every click brings you closer to certification — or further from sanity.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Stats Dashboard
&lt;/h3&gt;

&lt;p&gt;Because no government bureau is complete without meaningless metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Total Clicks&lt;/strong&gt; — the only number that matters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clicks Per Second&lt;/strong&gt; — your raw throughput&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accuracy™&lt;/strong&gt; — a completely fabricated percentage that goes up the more you click, because we believe in positive reinforcement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Click Power&lt;/strong&gt; — a multiplier that grows every 100 clicks, achieving absolutely nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Clearance Level System
&lt;/h3&gt;

&lt;p&gt;As you click, you ascend through increasingly dramatic security clearances:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Clicks&lt;/th&gt;
&lt;th&gt;Clearance Level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;UNCLASSIFIED&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;CONFIDENTIAL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;SECRET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;td&gt;TOP SECRET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;COSMIC TOP SECRET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;ULTRA MEGA SECRET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,500&lt;/td&gt;
&lt;td&gt;BEYOND CLASSIFICATION&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;td&gt;CLICK DEITY&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each level has its own color, its own glow, and its own sense of hollow accomplishment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Six Certification Examinations
&lt;/h3&gt;

&lt;p&gt;This is where it gets &lt;em&gt;interactive&lt;/em&gt;. Each exam unlocks at a certain click threshold and tests a completely useless skill:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 1: Speed Clicking Qualification&lt;/strong&gt;&lt;br&gt;
Click 20 times in 5 seconds. Prove you are not, in fact, a sloth. This is your warm-up. If you fail this, the Bureau recommends a different career path (perhaps data entry, where the stakes are somehow even lower).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 2: Precision Click Assessment&lt;/strong&gt;&lt;br&gt;
Five targets appear on screen. They shrink. You must hit them. Missing is described as "a federal offense" in the fine print (it isn't, but the guilt is real). The targets get smaller with each hit because the Bureau believes in escalating psychological pressure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 3: Patience Endurance Trial&lt;/strong&gt;&lt;br&gt;
Hold a button for &lt;em&gt;exactly&lt;/em&gt; 7.0 seconds. Not 6.5. Not 7.8. Seven. There's a ±0.5 second tolerance because the Bureau is generous (they aren't, but the lawyers insisted). This exam has a 73% failure rate among developers because we are physically incapable of waiting for anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 4: Rhythmic Click Synchronization&lt;/strong&gt;&lt;br&gt;
A circle pulses. You click when it lights up. It's like Guitar Hero but with a single button, no music, and no fun. Score 6 out of 10 to pass. The Bureau considers anything below that "arrhythmic clicking" which is, apparently, a diagnosable condition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 5: Morse Code Transmission&lt;/strong&gt;&lt;br&gt;
Tap out S.O.S. in Morse code (short-short-short, long-long-long, short-short-short). This is the exam where most people realize they have no idea what Morse code actually is, despite confidently claiming otherwise at dinner parties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EXAM 6: The Trust Exercise&lt;/strong&gt;&lt;br&gt;
This is my favorite. A big red button appears. It says "DON'T CLICK." You must resist for 10 seconds. While the system actively taunts you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Come on, just one little click..."&lt;br&gt;
"Nobody's watching... 👀"&lt;br&gt;
"Your finger is already moving, isn't it?"&lt;br&gt;
"Other test subjects all failed at this point..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you click it, a giant "DENIED" stamp slams across your screen. You deserve it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus Chaos
&lt;/h3&gt;

&lt;p&gt;Because six exams weren't enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Combo System&lt;/strong&gt;: Click fast enough and a combo counter appears. Hit 20x for the "UNSTOPPABLE" achievement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;13+ Achievements&lt;/strong&gt;: From "First Click" (participation trophy energy) to "CERTIFIED CLICKER" (you completed all exams and should probably go outside).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random Events&lt;/strong&gt;: A 3% chance per click that the system flags you with alerts like "SUSPICIOUS CLICKING PATTERN DETECTED" or "Agent #404 is now monitoring this session."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The DENIED Stamp&lt;/strong&gt;: Randomly slams across your screen with a satisfying animation, just to keep you on your toes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Particle Explosions&lt;/strong&gt;: Every click spawns emoji particles. Higher combos = more particles. Your screen will look like a New Year's Eve celebration at a bureaucratic office party.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confetti&lt;/strong&gt;: At every milestone (10, 50, 100, 500, 1000 clicks), confetti rains down. Because positive reinforcement is free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret Keyboard Shortcuts&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt; — Activates Dance Mode (the entire page starts shifting through hue rotations while the button spins maniacally)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;T&lt;/strong&gt; — Thermal Vision (high saturation + contrast, makes the button look like a heat signature)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt; — Secret bonus: awards 50 clicks instantly and fires confetti&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Certificate
&lt;/h3&gt;

&lt;p&gt;Complete all six exams and you receive an official &lt;strong&gt;Certificate of Completion&lt;/strong&gt; — a parchment-textured, gold-bordered, absolutely meaningless document that declares you a "Certified Professional Button Clicker (CPBC)."&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your name (you get to type it in, very official)&lt;/li&gt;
&lt;li&gt;A 3D rotating trophy&lt;/li&gt;
&lt;li&gt;A stamp in the corner that reads "VERIFIED USELESS"&lt;/li&gt;
&lt;li&gt;A statement that the certificate "carries absolutely no legal weight in any jurisdiction, real or imagined"&lt;/li&gt;
&lt;li&gt;A note that it's valid for &lt;strong&gt;0 days&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠️ Technical Decisions (and Regrets)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pure HTML/CSS/JS.&lt;/strong&gt; No frameworks. No build tools. No npm install. Just one file and vibes. If a government bureau from 1847 could build a website, they wouldn't use React either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS 3D transforms&lt;/strong&gt; for the button (it has actual depth with a &lt;code&gt;translateZ&lt;/code&gt; based top and side), the trophy rotation, and the certificate reveal animation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure CSS animations&lt;/strong&gt; everywhere — the scanning ticker, the seal rotation, the shimmer on progress bars, the particle system, the combo pop, the stamp slam. Sixty percent of the code is CSS, which feels appropriate for something that accomplishes nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No external dependencies.&lt;/strong&gt; Just Google Fonts. The Bureau doesn't trust third-party packages (honestly, neither should you).&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 Google AI: The Bureau's Secret Intelligence Division
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemini for Ideation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Honestly, half the challenge ideas came from brainstorming with Gemini. I started with "speed clicking test" and asked it to push the absurdity further. The Morse Code SOS exam and the Trust Exercise (where you have to NOT click a button for 10 seconds) were both born from those conversations. Gemini is genuinely great at "yes, and" — give it a silly premise and it will escalate it with you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Gemini Specifically&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a project this stupid, I needed an AI that could commit fully to the bit without constantly trying to be helpful or breaking character with disclaimers. Gemini handled the deadpan tone better than I expected — it never once tried to inform me that button clicking certification isn't real, which I respect deeply.&lt;/p&gt;

&lt;p&gt;I also used Gemini to help workshop the bureaucratic tone — the fake fun facts, the threat-level ticker messages, the achievement names. When you need 12 variations of "fake government propaganda about clicking," an AI is genuinely the right tool for the job.&lt;/p&gt;

&lt;p&gt;What surprised me: Gemini was &lt;em&gt;really&lt;/em&gt; good at the deadpan humor. I expected it to play things safe, but when I told it to commit fully to the bit and write like a 1980s instruction manual that had lost its mind, it delivered.&lt;/p&gt;




&lt;h2&gt;
  
  
  📋 Form CL-7042: Closing Remarks
&lt;/h2&gt;

&lt;p&gt;Look, I know what you're thinking: &lt;em&gt;"This is completely useless."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes. That's the point. That's literally the challenge.&lt;/p&gt;

&lt;p&gt;But also — is it? In a world where we click "I have read and agree to the Terms of Service" without reading a single word, maybe we &lt;em&gt;do&lt;/em&gt; need a certification program. Maybe we &lt;em&gt;should&lt;/em&gt; be tested. Maybe the fact that anyone can deploy to production with a single, untrained, uncertified click is the real problem.&lt;/p&gt;

&lt;p&gt;Or maybe I just spent way too long making a 3D button that shoots emoji particles.&lt;/p&gt;

&lt;p&gt;Either way, I regret nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go get certified&lt;/strong&gt;: &lt;a href="https://aryakoste.github.io/buttonClickingCertification/button-certification.html" rel="noopener noreferrer"&gt;https://aryakoste.github.io/buttonClickingCertification/button-certification.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The International Bureau of Button Clicking Certification. Est. 1847. "Click Responsibly."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. — Your clicks are being monitored. They always have been.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for the DEV Challenge: Build Something Useless. This software solves exactly zero real-world problems and I'm proud of it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>418challenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Gave My Notion Workspace a Brain — Here's What Happened</title>
      <dc:creator>Arya Koste</dc:creator>
      <pubDate>Sun, 29 Mar 2026 19:43:20 +0000</pubDate>
      <link>https://dev.to/arya_koste_5845807df94776/i-gave-my-notion-workspace-a-brain-heres-what-happened-3ehp</link>
      <guid>https://dev.to/arya_koste_5845807df94776/i-gave-my-notion-workspace-a-brain-heres-what-happened-3ehp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I spent a weekend building an AI that autonomously operates inside Notion. You type one sentence. It searches your workspace, reasons about what to do, creates pages, spins up databases, populates tasks — and you watch every single step happen in real time. Here's how I built it and why it might be the most useful thing I've ever made.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Frustration That Started This
&lt;/h2&gt;

&lt;p&gt;I've been a Notion user. My workspace is a sprawling mess of half-finished project pages, meeting notes that never got followed up on, and goals I set in January that I haven't looked at since.&lt;/p&gt;

&lt;p&gt;The problem isn't Notion. Notion is incredible. The problem is that &lt;strong&gt;Notion is entirely passive&lt;/strong&gt;. It sits there, waiting. It does nothing unless you tell it exactly what to do, how to structure it, and where to put it.&lt;/p&gt;

&lt;p&gt;I kept thinking: what if Notion could just... &lt;em&gt;think&lt;/em&gt;? What if I could say "set up a Q2 OKR tracking system" and it would actually &lt;em&gt;do&lt;/em&gt; it — build the database, create the objectives, set up the weekly check-in templates, write the guidelines page — without me having to architect every single piece?&lt;/p&gt;

&lt;p&gt;That's Apex.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Apex&lt;/strong&gt; is a full-stack AI command center that gives &lt;strong&gt;Llama 3.3 70B (via Groq)&lt;/strong&gt; complete autonomous access to your Notion workspace. You chat with it naturally. It reasons, plans, and executes — using 10 Notion MCP tools in sequence — while you watch every operation happen live on screen.&lt;/p&gt;

&lt;p&gt;It's not a chatbot that tells you &lt;em&gt;how&lt;/em&gt; to use Notion. It's an agent that &lt;em&gt;uses&lt;/em&gt; Notion for you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why Groq?&lt;/strong&gt; Groq's free tier gives you 14,400 requests/day with no credit card required. Llama 3.3 70B handles tool calling excellently — and it's genuinely fast. Zero cost to run.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  7 Intelligent Modes
&lt;/h3&gt;

&lt;p&gt;Each mode has a deeply engineered system prompt that makes the AI behave like a domain specialist:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;💬 &lt;strong&gt;Chat&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Natural language Q&amp;amp;A — ask anything about your workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏗️ &lt;strong&gt;Project Blueprint&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;One sentence → hub page + task database + timeline + risk log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧠 &lt;strong&gt;Brain Dump&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Raw chaos → organized, searchable Notion knowledge base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📋 &lt;strong&gt;Meeting Intelligence&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Paste notes → decisions, action items, owners, follow-ups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📊 &lt;strong&gt;Weekly Review&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AI scans entire workspace → CEO-level intelligence report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎯 &lt;strong&gt;OKR Tracker&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Define goals → complete OKR system with check-in templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📅 &lt;strong&gt;Content Calendar&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Describe strategy → 20+ content ideas across all channels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Feature I'm Most Proud Of: Live Tool Use Visualization
&lt;/h2&gt;

&lt;p&gt;Most AI tools are black boxes. You ask, you wait, you get an answer. You have no idea what happened in between.&lt;/p&gt;

&lt;p&gt;Apex flips this. As the AI works, you see every single Notion operation as it happens — animated cards streaming in, showing you exactly what the AI is doing and why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🌐  Scanning Workspace...                          ✓ 12 pages, 3 databases
🔍  Searching Notion...     "Q2 goals"             ✓ Found 2 related pages
📄  Creating Page...        "🎯 Q2 Goals Hub"      ✓ Created
🗄️  Building OKR Database.. "Q2 OKR Tracker"       ✓ Created with 8 fields
➕  Adding Record...        "Grow MRR by 40%"      ✓ Objective added
➕  Adding Record...        "Key Result: MRR &amp;gt; $50k"✓ KR added
📄  Creating Page...        "📋 Weekly Check-in"   ✓ Template created
📄  Creating Page...        "🏆 Wins &amp;amp; Learnings"  ✓ Log created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight Notion operations, chained intelligently, executed autonomously. The whole OKR system is live in your workspace before you've finished reading the output.&lt;/p&gt;

&lt;p&gt;This transparency is what makes it feel like a genuine AI colleague rather than a magic trick.&lt;/p&gt;




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

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




&lt;h2&gt;
  
  
  Technical Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 14 (App Router) + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS with glassmorphism, animated ambient orbs, dot-grid background&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: Llama 3.3 70B via Groq SDK — streaming + multi-turn tool use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt;: &lt;code&gt;@notionhq/client&lt;/code&gt; — 10 MCP-compatible tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport&lt;/strong&gt;: Server-Sent Events for real-time streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The 10 Notion MCP Tools
&lt;/h3&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;NOTION_TOOLS&lt;/span&gt; &lt;span class="o"&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;search_notion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Full-text search across workspace&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_notion_page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Create pages with rich markdown → block conversion&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_page_content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Read any page's full content&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update_notion_page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Append structured content to existing pages&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_project_database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Task DB: Status, Priority, Due Date, Assignee, Tags&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query_database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Retrieve records with optional filters&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get_workspace_overview&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Scan recent pages and databases&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add_database_item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Insert rows with proper property types&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_goals_tracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// OKR DB: Objective/KR, Progress %, Owner, Quarter&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_content_calendar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Editorial DB: Channel, Type, Status, Publish Date&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools are defined in OpenAI-compatible format (which Groq uses), so each tool looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create_notion_page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create a new page in Notion with rich content...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title of the new page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Page content in markdown format...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Emoji icon for the page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&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;content&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Multi-Turn Streaming Loop
&lt;/h3&gt;

&lt;p&gt;This is the core of the whole thing. The AI doesn't just make one tool call — it chains as many as needed, reasoning about results before deciding what to do next:&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;groq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Groq&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GROQ_API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Keep looping until the model stops using tools&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streamResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;groq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;llama-3.3-70b-versatile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NOTION_TOOLS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Accumulate streamed tool call fragments&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &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;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;streamResponse&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;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&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;delta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Stream text chunks to the frontend in real-time&lt;/span&gt;
      &lt;span class="nf"&gt;sendSSE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Accumulate streamed JSON fragments per tool call index&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arguments&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&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;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&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;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCallsMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No more tool calls — done&lt;/span&gt;

  &lt;span class="c1"&gt;// Notify frontend each tool is starting&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendSSE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&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="c1"&gt;// Add assistant message with tool_calls to history&lt;/span&gt;
  &lt;span class="nx"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&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="c1"&gt;// Execute each tool against the Notion API&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;sendSSE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool_result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Feed result back as a tool message for the next reasoning step&lt;/span&gt;
    &lt;span class="nx"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tool_call_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: the AI sees the &lt;em&gt;result&lt;/em&gt; of each Notion operation before deciding what to do next. It's not blindly firing off a pre-planned list — it's actually reading what it created and adapting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Markdown → Notion Blocks
&lt;/h3&gt;

&lt;p&gt;One of the trickier engineering challenges: Notion's API doesn't accept markdown. Every page has to be structured as typed block objects. I wrote a full converter that handles 8 block types:&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;markdownToBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NotionBlock&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;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;# &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;heading1Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;## &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;heading2Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;### &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;heading3Block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;- &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;bulletBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt; /&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&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="nf"&gt;numberedBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&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;/^&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt; /&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;quoteBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dividerBlock&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;paragraphBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headings, bullets, numbered lists, quotes, dividers, paragraphs — all properly typed and sent to Notion's API.&lt;/p&gt;

&lt;h3&gt;
  
  
  OKR Database Schema
&lt;/h3&gt;

&lt;p&gt;When you say "set up my Q2 OKRs", Apex builds a database with exactly the right schema:&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;okrSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&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;Objective&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;Key Result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&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;Not Started&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;On Track&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;At Risk&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;Behind&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;Complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;percent&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;Owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rich_text&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Due Date&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;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Quarter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;period&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;Notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a generic template. A purpose-built structure that matches how real OKR systems work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Features That Make the UX Sing
&lt;/h2&gt;

&lt;p&gt;Beyond the core AI functionality, I obsessed over the interface:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⌘K Command Palette&lt;/strong&gt; — Hit Cmd+K to open a searchable command palette. Switch modes, fire quick actions, export your conversation, clear the session — all without touching the mouse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slash Commands&lt;/strong&gt; — Type &lt;code&gt;/project&lt;/code&gt;, &lt;code&gt;/goals&lt;/code&gt;, &lt;code&gt;/meeting&lt;/code&gt; directly in the chat input to instantly switch modes. Feels like a native app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voice Input&lt;/strong&gt; — Click the mic button and just talk. Web Speech API converts your speech to text in real time, with animated pulse rings showing it's listening. Perfect for actual brain dumps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart Follow-Up Suggestions&lt;/strong&gt; — After every AI response, three contextual next-step prompts appear. Click any to instantly continue. It's like the AI is guiding you toward the next useful thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session Stats Bar&lt;/strong&gt; — A live counter in the top bar shows pages created, databases built, and total Notion operations performed this session. It's a small thing but makes the productivity feel visceral.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Floating Success Toasts&lt;/strong&gt; — Every time a Notion page or database is created, a beautiful animated toast slides up: &lt;em&gt;"Page created: Q2 OKR Tracker — saved to Notion"&lt;/em&gt;. Instant feedback, no hunting through your workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export Conversation&lt;/strong&gt; — Download your entire session as a formatted markdown file. Great for sharing what you built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workspace Dashboard&lt;/strong&gt; — A separate &lt;code&gt;/dashboard&lt;/code&gt; route showing live workspace stats, recent pages and databases, a full capability showcase, and a visual "How Apex Works" flow diagram.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Demo: OKR Mode in Action
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What I typed:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Set up Q2 OKRs — grow MRR by 40%, improve activation rate to 60%, ship mobile v1, and reduce churn below 3%"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What Apex did (30 seconds):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scanned workspace for existing goal-related pages&lt;/li&gt;
&lt;li&gt;Created a &lt;code&gt;🎯 Goals Hub&lt;/code&gt; page with vision statement and OKR methodology explained&lt;/li&gt;
&lt;li&gt;Built the &lt;code&gt;Q2 OKR Tracker&lt;/code&gt; database with 8 properly typed fields&lt;/li&gt;
&lt;li&gt;Added each of the 4 objectives as database records (Type: Objective)&lt;/li&gt;
&lt;li&gt;Added 3 key results per objective (Type: Key Result, with measurable targets)&lt;/li&gt;
&lt;li&gt;Created a &lt;code&gt;📋 Weekly Check-in Template&lt;/code&gt; page — duplicate this every Monday&lt;/li&gt;
&lt;li&gt;Created a &lt;code&gt;🏆 Wins &amp;amp; Learnings Log&lt;/code&gt; for retrospectives&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What I had to do:&lt;/strong&gt; Type one sentence.&lt;/p&gt;

&lt;p&gt;That's the whole thing.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled Reports&lt;/strong&gt; — Auto-run weekly review every Monday morning via cron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion Webhooks&lt;/strong&gt; — React to workspace changes in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-workspace&lt;/strong&gt; — Switch between different Notion accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template Library&lt;/strong&gt; — Pre-built prompts for 20+ common use cases&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Setup in 3 Minutes
&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/Aryakoste/apex-notion
&lt;span class="nb"&gt;cd &lt;/span&gt;apex-notion-ai
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.local.example .env.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;.env.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GROQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gsk_...
&lt;span class="nv"&gt;NOTION_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;span class="c"&gt;# Open http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Getting your Groq API key (free):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://console.groq.com" rel="noopener noreferrer"&gt;console.groq.com&lt;/a&gt; → sign up (no credit card)&lt;/li&gt;
&lt;li&gt;API Keys → Create API Key → copy it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Getting your Notion API key:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://notion.so/my-integrations" rel="noopener noreferrer"&gt;notion.so/my-integrations&lt;/a&gt; → New integration → copy the secret&lt;/li&gt;
&lt;li&gt;On each Notion page: &lt;code&gt;...&lt;/code&gt; menu → &lt;strong&gt;Add connections&lt;/strong&gt; → select your integration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Everything else is handled by Apex.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters Beyond the Demo
&lt;/h2&gt;

&lt;p&gt;The reason I'm excited about this isn't the cool UI or the streaming animations — it's what it represents for how we use tools.&lt;/p&gt;

&lt;p&gt;Notion has always required you to be the architect. You had to know what a good project page looks like. You had to remember to run your weekly review. You had to structure your OKRs correctly.&lt;/p&gt;

&lt;p&gt;With MCP, the AI can be the architect. You just have to know &lt;em&gt;what you want&lt;/em&gt;. The gap between intention and implementation collapses from hours to seconds.&lt;/p&gt;

&lt;p&gt;I think this is the future of productivity software. Not AI that suggests. AI that &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;Full source code: &lt;strong&gt;[&lt;a href="https://github.com/Aryakoste/apex-notion" rel="noopener noreferrer"&gt;https://github.com/Aryakoste/apex-notion&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;#notionmcp&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#nextjs&lt;/code&gt; &lt;code&gt;#typescript&lt;/code&gt; &lt;code&gt;#groq&lt;/code&gt; &lt;code&gt;#llama&lt;/code&gt; &lt;code&gt;#productivity&lt;/code&gt; &lt;code&gt;#buildinpublic&lt;/code&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
