<?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: ujja</title>
    <description>The latest articles on DEV Community by ujja (@ujja).</description>
    <link>https://dev.to/ujja</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%2F439943%2F774d42c6-75e1-4247-8688-30020ce8f40a.png</url>
      <title>DEV Community: ujja</title>
      <link>https://dev.to/ujja</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ujja"/>
    <language>en</language>
    <item>
      <title>I Love Dogs, But Dogs Scare Me — So I Built Pawsitive with Gemini 🐾.</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:28:28 +0000</pubDate>
      <link>https://dev.to/ujja/i-love-dogs-but-dogs-scare-me-so-i-built-pawsitive-with-gemini--1536</link>
      <guid>https://dev.to/ujja/i-love-dogs-but-dogs-scare-me-so-i-built-pawsitive-with-gemini--1536</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are only two types of people in the world: those who love dogs, and those who love them from a distance. 😂🐶&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;I love dogs.&lt;/p&gt;

&lt;p&gt;There, I said it.&lt;/p&gt;

&lt;p&gt;And yet, I am also scared of them.&lt;/p&gt;

&lt;p&gt;It is a strange combination. I can happily watch dog videos for hours, admire every dog I see on the street, and still instinctively tense up when one suddenly runs towards me.&lt;/p&gt;

&lt;p&gt;A lot of that comes from a traumatic experience I had with a dog as a child. You can grow up knowing that one experience doesn't define every dog you will ever meet, but sometimes your instincts don't get the memo.&lt;/p&gt;

&lt;p&gt;While thinking about this challenge, I started wondering if the problem was partly &lt;strong&gt;not understanding what I was seeing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If a dog is wagging its tail, what does that actually mean? If it is staring at me, should I move away? If it is barking, is it excited, nervous, protective, or something else? And if a dog is approaching me on a footpath, what should I actually do?&lt;/p&gt;

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

&lt;p&gt;That question became &lt;strong&gt;Pawsitive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pawsitive is an interactive learning app for people who feel nervous around dogs. Instead of telling people not to be afraid, it tries to make encounters feel less unpredictable by teaching them how to recognise common body-language signals, understand situations, and make calmer decisions.&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%2F1620gicx87zoj32d5pue.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%2F1620gicx87zoj32d5pue.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But then I realised there was another side to the interaction.&lt;/p&gt;

&lt;p&gt;A dog owner might see their dog happily walking towards someone and think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Don't worry, he's friendly!"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The person approaching might be thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Please don't let that dog come any closer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both people can be looking at the same dog while experiencing completely different situations.&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%2F2jne5ulf7zvh9i71zbeq.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%2F2jne5ulf7zvh9i71zbeq.png" alt=" " width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So Pawsitive has two learning paths: &lt;strong&gt;people who are nervous around dogs and dog owners&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first helps people understand dogs and build confidence. The second helps owners recognise when someone might be uncomfortable, why giving people space matters, and why "my dog is friendly" doesn't necessarily make an approaching dog less intimidating.&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%2Fpn8tbzsefilxqceryl1r.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%2Fpn8tbzsefilxqceryl1r.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That became the idea behind the whole app:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two perspectives. One better interaction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;Github link: &lt;a href="https://github.com/ujjavala/pawsitive" rel="noopener noreferrer"&gt;https://github.com/ujjavala/pawsitive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vercel deployment: &lt;a href="https://pawsitive-nine.vercel.app/" rel="noopener noreferrer"&gt;https://pawsitive-nine.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The Vercel deployment does not include a Gemini API key. On supported desktop Chrome devices, &lt;strong&gt;Understand This Dog&lt;/strong&gt; falls back to Chrome's built-in Gemini Nano model and analyses the photo privately on-device. You can also run the app locally and add &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; to &lt;code&gt;.env.local&lt;/code&gt; for server-side Gemini analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The experience begins with three gentle onboarding questions: how comfortable you are around unfamiliar dogs, which situations feel most uncomfortable, and what you want to achieve. Someone who selects "Very nervous" isn't immediately thrown into a stressful scenario. They start by learning the basics, exploring relaxed and tense body language before gradually moving into realistic situations. Their answer also sets an initial confidence score, but it never locks content or forces them into an interaction.&lt;/p&gt;

&lt;p&gt;Then comes &lt;strong&gt;What Would You Do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might be walking down a footpath when a dog approaches with its owner. Instead of reading instructions, you decide how you would respond and then get an explanation of why one choice may lead to a calmer interaction.&lt;/p&gt;

&lt;p&gt;The perspective can then switch to the owner's side. The same encounter looks very different when you realise that giving someone space may be more helpful than reassuring them that your dog is friendly.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;Understand This Dog&lt;/strong&gt; lets users upload a dog photo and use Gemini to explore the visible body-language cues. The result also suggests a possible breed or breed mix, explains which visible features informed the estimate, and shares typical physical characteristics and cautiously worded breed-level behavioural tendencies. It uses the configured server model when a Gemini key is available and falls back to Chrome's built-in Gemini Nano model when the server has no key and the browser supports multimodal on-device AI.&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%2Fux70ta2yxdl1p9q0bn6w.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%2Fux70ta2yxdl1p9q0bn6w.png" alt=" " width="799" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The entire experience is designed to be short enough to explore in a few minutes while still telling the complete story.&lt;/p&gt;

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

&lt;p&gt;I wanted Pawsitive to feel like something you would actually want to use, rather than a clinical safety course.&lt;/p&gt;

&lt;p&gt;That led to a bright, playful interface, an animated SVG dog, playful scenario scenes, gentle sounds, small celebrations, and a mascot named &lt;strong&gt;Pip&lt;/strong&gt; who follows the user through the experience.&lt;/p&gt;

&lt;p&gt;The app is built with &lt;strong&gt;React, TypeScript and Vite&lt;/strong&gt;, with &lt;strong&gt;Tailwind CSS and Radix UI primitives&lt;/strong&gt; for the interface, &lt;strong&gt;Motion for React&lt;/strong&gt; (the current Framer Motion package) for animation, SVG illustrations for Pip, and &lt;strong&gt;Zustand with localStorage&lt;/strong&gt; for progress tracking. &lt;strong&gt;Zod&lt;/strong&gt; validates structured AI responses at runtime, while &lt;strong&gt;Lucide&lt;/strong&gt; provides the icon system. Server inference uses Google's &lt;code&gt;@google/genai&lt;/code&gt; SDK, while private inference uses Chrome's built-in &lt;code&gt;LanguageModel&lt;/code&gt; Prompt API without adding another model runtime to the application bundle.&lt;/p&gt;

&lt;p&gt;There is deliberately no authentication or database in the MVP. You can open the app and start learning immediately.&lt;/p&gt;

&lt;p&gt;The weekend build includes &lt;strong&gt;10 lessons for nervous users, six owner lessons, eight scenarios, eight body-language signals and five gentle achievements&lt;/strong&gt;. Routes are lazy-loaded so the non-critical learning, scenario and AI screens do not all have to load up front.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Pip feel alive
&lt;/h3&gt;

&lt;p&gt;Pip changes depending on what is happening. They can tilt their head during a question, wag their tail after a correct answer, bounce when a lesson is completed, or look confused when something goes wrong.&lt;/p&gt;

&lt;p&gt;The animations are deliberately gentle. There are no sudden dogs jumping towards the screen or unexpected barking, because an app designed for nervous dog lovers shouldn't accidentally make them nervous.&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%2Fz84xo0804h9gp6j9dkpu.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%2Fz84xo0804h9gp6j9dkpu.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same thinking shaped the sound design. The current build uses short, synthesised feedback tones for correct answers, lesson completion and soft errors. I deliberately left barking and other dog sounds out of the learning flow so the app cannot unexpectedly startle someone. Sound can be disabled, and motion can be reduced from inside the app. Pip also respects the device's own reduced-motion preference.&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%2Fxho88m7itul4q5n8xz2w.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%2Fxho88m7itul4q5n8xz2w.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is simple: &lt;strong&gt;make the app feel alive without making it overwhelming.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 The Gemini Experiment
&lt;/h2&gt;

&lt;p&gt;The most interesting part of Pawsitive is &lt;strong&gt;Understand This Dog&lt;/strong&gt;, where I wanted AI to do something more meaningful than power another generic chatbot.&lt;/p&gt;

&lt;p&gt;A user can upload a JPG, PNG or WebP photo of a dog, and Gemini analyses the visible signals in the image. The response is structured into six parts:&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%2Fhf7mz71mbqg8e5qfklln.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%2Fhf7mz71mbqg8e5qfklln.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Possible breed&lt;/strong&gt; — an appearance-based breed or breed-mix estimate, the visible traits behind it, and an honest visual-confidence level. If the image is not clear enough, the model can simply return &lt;strong&gt;Breed unclear&lt;/strong&gt; instead of forcing a guess.&lt;/p&gt;

&lt;p&gt;The breed section also includes typical physical characteristics and possible breed-level behaviours. These are written as broad tendencies using language such as "often", "may", and "commonly"—never as claims about the individual dog. A clear reminder explains that breed cannot predict this dog's personality, emotional state, safety, or future behaviour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we can see&lt;/strong&gt; — observable cues such as posture, ears, tail visibility and body position, each with a visual-confidence level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this might mean&lt;/strong&gt; — cautious interpretations of those signals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we can't know&lt;/strong&gt; — because one photograph cannot tell us everything about a dog's emotional state or predict what it will do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you can do&lt;/strong&gt; — conservative guidance focused on giving unfamiliar dogs appropriate space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety note&lt;/strong&gt; — an explicit reminder not to approach an unfamiliar dog based only on an image interpretation.&lt;/p&gt;

&lt;p&gt;That last part was especially important to me.&lt;/p&gt;

&lt;p&gt;I didn't want to build an AI-powered "Is this dog safe?" detector. A photograph cannot reliably tell you that a dog is safe, friendly, or won't bite.&lt;/p&gt;

&lt;p&gt;So the Gemini integration is deliberately designed to acknowledge uncertainty, using language such as "may indicate" and "can be consistent with" rather than making definitive claims. That same principle applies to breed identification: visual similarity can suggest a breed or mix, but appearance alone cannot confirm ancestry or tell us how an individual dog will behave.&lt;/p&gt;

&lt;p&gt;That safety work does not rely only on the interface. In the server path, the browser sends the image to a same-origin &lt;strong&gt;Vercel Function&lt;/strong&gt;, so the Gemini key is never exposed to client code. The function rejects unsupported files and images larger than 5 MB before calling Gemini, requests JSON matching a schema, validates the result with Zod, and rejects responses containing certainty claims such as "definitely friendly", "won't bite" or "safe to approach". The UI validates the response again before rendering it as cards, never as model-generated HTML.&lt;/p&gt;

&lt;p&gt;The Gemini model is configured server-side with &lt;code&gt;GEMINI_MODEL&lt;/code&gt; and checked against an allowlist. The current default is &lt;strong&gt;Gemini 3.5 Flash-Lite&lt;/strong&gt;, and unsupported model IDs fall back to this default rather than being passed through to the provider. For local development, Vite loads only the server-side Gemini environment variables and mounts the same &lt;code&gt;/api/analyze-dog&lt;/code&gt; handler used in deployment. That means &lt;code&gt;npm run dev&lt;/code&gt; runs the UI and local API together without requiring the Vercel CLI or a Vercel login.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private on-device fallback with Gemini Nano
&lt;/h3&gt;

&lt;p&gt;When the server reports that no &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; is configured, Pawsitive checks Chrome's built-in &lt;code&gt;LanguageModel&lt;/code&gt; API with the exact text-and-image capabilities required by the feature. If the model is ready, the selected image is analysed locally. If Chrome needs to download the model first, the interface shows download progress and provides a cancel action.&lt;/p&gt;

&lt;p&gt;The on-device session receives the same cautious system instruction as server Gemini, accepts the selected photo as an image &lt;code&gt;Blob&lt;/code&gt;, and returns JSON constrained by the same schema. That schema includes the possible breed, confidence, supporting visual evidence, typical characteristics, possible breed behaviours, body-language observations, uncertainty, and safety guidance. Its output passes through the same Zod validation and certainty checks before anything is displayed. The session is destroyed after analysis to release browser resources, and successful results are clearly labelled &lt;strong&gt;analysed privately on this device&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What amazed me about the private mode
&lt;/h3&gt;

&lt;p&gt;This became one of the most surprising parts of the whole project. I expected the private on-device mode to be a useful fallback, but I did not expect Gemini Nano to perform this well. In my testing, its observations and cautious interpretations were often remarkably close to the results produced through the API-key-powered server models.&lt;/p&gt;

&lt;p&gt;API mode:&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%2Flz1z1nm67uid0ctpf5xj.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%2Flz1z1nm67uid0ctpf5xj.png" alt=" " width="799" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Private mode:&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%2Fj3vfxe1c98nwajj0lvvq.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%2Fj3vfxe1c98nwajj0lvvq.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That feels like a small glimpse of how quickly AI is advancing: a browser can now examine an image, follow a detailed instruction, produce structured output and respect the same safety constraints—all without an API key and without sending the photo to a server. The privacy benefit is not achieved by giving up the core experience; the result remains genuinely useful.&lt;/p&gt;

&lt;p&gt;There are still differences between devices and models, so I would not call the two paths identical or treat my experiments as a formal benchmark. Even so, seeing capable multimodal AI run privately inside Chrome was a real &lt;strong&gt;wow moment&lt;/strong&gt; for me. Gemini Nano started as a fallback and ended up feeling like one of Pawsitive's most exciting features—and a sign of the possibilities that on-device AI is beginning to unlock.&lt;/p&gt;

&lt;p&gt;The fallback order is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use server Gemini when &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; is configured.&lt;/li&gt;
&lt;li&gt;Use Chrome's built-in Gemini Nano model when the key is absent and the requested multimodal session is supported.&lt;/li&gt;
&lt;li&gt;Show an honest unavailable message and retain the clearly labelled seeded demo when neither option is available.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Prerequisites for private on-device analysis
&lt;/h3&gt;

&lt;p&gt;Chrome's foundation-model APIs are a progressive enhancement rather than universal browser functionality. The current documented requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser:&lt;/strong&gt; Google Chrome 148 or newer for the web Prompt API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operating system:&lt;/strong&gt; Windows 10 or 11, macOS 13 or newer, Linux, or a Chromebook Plus device. Chrome on Android, iOS, and non-Plus Chromebooks is not currently supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; At least 22 GB free on the volume containing the Chrome profile. Chrome removes the model if available storage later drops below 10 GB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute:&lt;/strong&gt; Strictly more than 4 GB GPU VRAM, or at least 16 GB system RAM and four CPU cores for CPU execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network:&lt;/strong&gt; An unmetered connection for the initial browser-managed model download. Subsequent inference can run without a network connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User activation:&lt;/strong&gt; The first download must be started after a meaningful user interaction, which Pawsitive provides through the &lt;strong&gt;Help me understand&lt;/strong&gt; button.&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%2F23xpnsd082nbnhzifz02.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%2F23xpnsd082nbnhzifz02.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For local testing, enable &lt;code&gt;chrome://flags/#optimization-guide-on-device-model&lt;/code&gt; and &lt;code&gt;chrome://flags/#prompt-api-for-gemini-nano&lt;/code&gt;, then fully relaunch Chrome. Download and model errors can be inspected at &lt;code&gt;chrome://on-device-internals&lt;/code&gt;. After the initial download, Chrome states that inference is local and no prompt or image data is sent to Google or another third party.&lt;/p&gt;

&lt;p&gt;The AI feature also fails independently from the rest of the product. If neither server nor on-device analysis is available, the user sees a plain explanation and can continue learning. For challenge demos, there is a clearly labelled sample-photo experience with a seeded educational result; it never pretends to be a live result for a photo the user uploaded. Images are processed only after the user presses &lt;strong&gt;Help me understand&lt;/strong&gt;, and Pawsitive does not add them to application storage. On-device analysis keeps the image local; server analysis does not store it.&lt;/p&gt;

&lt;p&gt;AI isn't the authority here. &lt;strong&gt;It is the teacher.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Learning Journey
&lt;/h2&gt;

&lt;p&gt;The product principle follows a simple progression:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understand → Recognise → Respond → Build Confidence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual lesson content is organised into &lt;strong&gt;Understanding Dogs&lt;/strong&gt;, &lt;strong&gt;Meeting Dogs Safely&lt;/strong&gt;, and &lt;strong&gt;Thoughtful Ownership&lt;/strong&gt; modules. Users first learn about relaxed and tense body language, tail movement, barking, staring, and why individual signals shouldn't be interpreted in isolation.&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%2Fc6uwsyz0bidtpjjdoovy.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%2Fc6uwsyz0bidtpjjdoovy.png" alt=" " width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They then move into realistic scenarios and make decisions for themselves.&lt;/p&gt;

&lt;p&gt;If they get something wrong, Pawsitive doesn't throw a giant red &lt;strong&gt;WRONG&lt;/strong&gt; screen at them. Pip simply tilts their head and encourages them to look at the situation again.&lt;/p&gt;

&lt;p&gt;That was intentional.&lt;/p&gt;

&lt;p&gt;Someone who is already nervous doesn't need another reason to feel like they're failing.&lt;/p&gt;

&lt;p&gt;Progress follows the same principle. Onboarding starts confidence at 20, 40, 50, 70 or 85 depending on the user's own answer. Completing a lesson or correctly answering a new scenario adds five points, while an incorrect answer has no penalty. Rewards are idempotent, so repeating an activity cannot inflate the score. The Progress screen keeps confidence separate from completion and explicitly describes it as a personal reflection, not a clinical measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐕 The Owner Perspective
&lt;/h2&gt;

&lt;p&gt;The owner experience came directly from thinking about what makes dog encounters uncomfortable.&lt;/p&gt;

&lt;p&gt;A dog owner may genuinely have a friendly dog and genuinely believe nothing is wrong. But that doesn't necessarily change how the other person feels.&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%2Fpfcvsyna7yb0wrfi5aex.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%2Fpfcvsyna7yb0wrfi5aex.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One scenario asks an owner what they should do when they notice someone moving away from their dog.&lt;/p&gt;

&lt;p&gt;The answer isn't to follow them and explain that the dog is friendly. It's simply to create some space.&lt;/p&gt;

&lt;p&gt;That captures what I wanted Pawsitive to teach:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A safe interaction isn't just about understanding the dog. It's about understanding the human too.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The paired scenario is linked in both directions in the content model. Completing the person and owner sides unlocks the &lt;strong&gt;Perspective Shift&lt;/strong&gt; achievement, making the product's central idea visible in the progress journey rather than leaving it as marketing copy.&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%2Fyugycxcnx2ml9pcksnux.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%2Fyugycxcnx2ml9pcksnux.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Safety Net
&lt;/h2&gt;

&lt;p&gt;Because Pawsitive mixes educational content, stateful rewards and model output, I did not want the demo to depend only on a happy-path click-through.&lt;/p&gt;

&lt;p&gt;The project uses &lt;strong&gt;Vitest and React Testing Library&lt;/strong&gt;. The current automated suite checks the required lesson, scenario, signal and achievement counts; unique IDs and answer keys; two-way scenario links; confidence clamping and one-time rewards; and the dog-analysis schema. It also contains regression cases for unsafe AI phrases including "definitely friendly", "won't bite", "you can approach this dog" and "safe to approach".&lt;/p&gt;

&lt;p&gt;The on-device adapter tests cover unsupported browsers, successful structured multimodal output, browser model-download progress, session cleanup, and rejection of unsafe local-model certainty. A page-level test confirms that a missing server key selects the private path without uploading the image.&lt;/p&gt;

&lt;p&gt;Before submission, the strict TypeScript check, all &lt;strong&gt;20 automated tests&lt;/strong&gt;, ESLint, and the Vite production build pass.&lt;/p&gt;

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

&lt;p&gt;Pawsitive started with something personal: the slightly ridiculous feeling of loving dogs while still being afraid of them.&lt;/p&gt;

&lt;p&gt;I don't expect an app to erase that fear. That wasn't the goal.&lt;/p&gt;

&lt;p&gt;I wanted to build something that says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It's okay if you're nervous. Let's understand what's happening.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe knowing what to look for makes the next encounter feel a little less unpredictable. Maybe understanding that a wagging tail doesn't always mean "hello" gives someone a little more confidence. Maybe helping owners recognise when to create some distance makes the other person feel less alone in the situation.&lt;/p&gt;

&lt;p&gt;And maybe, eventually, someone who currently crosses the street when they see a dog can simply keep walking.&lt;/p&gt;

&lt;p&gt;For someone who is afraid of dogs, that might not feel like a small thing at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  🏆 Prize Category: Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;I'm submitting Pawsitive for &lt;strong&gt;Best Use of Google AI&lt;/strong&gt; because Gemini is part of the core learning experience rather than simply being added as a chatbot.&lt;/p&gt;

&lt;p&gt;Through &lt;strong&gt;Understand This Dog&lt;/strong&gt;, Gemini's multimodal capabilities help users explore visible dog body-language cues while deliberately teaching the limits of what can be inferred from a single image. The app can use a securely configured server model or Chrome's built-in Gemini Nano model, with shared structured-output and safety controls across both paths.&lt;/p&gt;

&lt;p&gt;A curious dog:&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%2Fw8e6oc12hw8i9dkt619o.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%2Fw8e6oc12hw8i9dkt619o.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A playful dog:&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%2Fspmo1yqb37bj5xe4sqnm.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%2Fspmo1yqb37bj5xe4sqnm.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A rare breed:&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%2Fsbso2wjwudadf8a1p2ts.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%2Fsbso2wjwudadf8a1p2ts.png" alt=" " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That uncertainty is part of the feature, not a limitation hidden from the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔮 Giving Pip a Voice with ElevenLabs
&lt;/h2&gt;

&lt;p&gt;The next piece I would love to add is a voice for Pip using &lt;strong&gt;ElevenLabs&lt;/strong&gt;. ElevenLabs is &lt;strong&gt;not part of the current weekend build&lt;/strong&gt;; this is a deliberate next step rather than a shipped integration.&lt;/p&gt;

&lt;p&gt;Right now, Pip is the animated companion reacting to the user's progress. With voice, Pip could become part of the learning journey itself.&lt;/p&gt;

&lt;p&gt;Instead of simply reading instructions, Pip could say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Okay, let's take this one slowly. You can see a dog coming towards you. What would you do?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After an answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Good thinking. Giving the dog and its owner a little more space can make this easier for everyone."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And for someone who is nervous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"There's no rush. Let's look at what's happening first."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The interesting part would be connecting Pip's voice to the user's confidence journey, so the experience gradually feels more encouraging and energetic as their confidence grows.&lt;/p&gt;

&lt;p&gt;That brings together three pieces of the experience:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini&lt;/strong&gt; understands the visual information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElevenLabs&lt;/strong&gt; gives Pip a natural voice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Motion for React&lt;/strong&gt; brings Pip to life.&lt;/p&gt;

&lt;p&gt;Together, they could turn Pip from an animated mascot into a genuine &lt;strong&gt;dog-confidence companion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I would keep that integration server-side, accept only a small set of approved narration IDs, and cache generated clips. That would protect the API key, control cost and prevent arbitrary text from turning the feature into an open text-to-speech proxy. Narration would remain user-initiated, optional and accompanied by the same visible text.&lt;/p&gt;

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

&lt;p&gt;There are plenty of directions Pawsitive could take, but the core idea would stay the same: gradually move people from illustrations to photographs, videos and increasingly realistic scenarios as their confidence grows.&lt;/p&gt;

&lt;p&gt;I also considered &lt;strong&gt;Snowflake&lt;/strong&gt;, but deliberately did not add it to the weekend MVP simply to increase the technology count. Pawsitive does not need a warehouse to teach a lesson or remember local progress. A future opt-in analytics layer could send anonymous events such as lesson completion, perspective switching, Gemini request success and labelled-demo usage to Snowflake—never uploaded photos, model response text or personal details. For now, keeping that out made the privacy story and the architecture simpler.&lt;/p&gt;

&lt;p&gt;For dog owners, there could eventually be more personalised guidance around their individual dogs and situations.&lt;/p&gt;

&lt;p&gt;But the weekend version is intentionally focused.&lt;/p&gt;

&lt;p&gt;Pawsitive is ultimately about closing the gap between two perspectives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I don't know what that dog is going to do."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"My dog is friendly. Why are they scared?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a lot of space between those two thoughts.&lt;/p&gt;

&lt;p&gt;I wanted to build something that helps close it.&lt;/p&gt;

&lt;p&gt;And, perhaps selfishly, something that might one day help me stop jumping every time a dog barks. &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%2Fgyv9o4271ddentm1i9yd.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%2Fgyv9o4271ddentm1i9yd.png" alt=" " width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You don't have to love dogs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;You just need to understand them.🐾 *&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>gemini</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Turning Ayurveda’s Six Rasas Into CSS Art</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sat, 15 Aug 2026 03:16:21 +0000</pubDate>
      <link>https://dev.to/ujja/rasa-turning-ayurvedas-six-rasas-into-css-art-4go6</link>
      <guid>https://dev.to/ujja/rasa-turning-ayurvedas-six-rasas-into-css-art-4go6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition, CSS Art&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;What does comfort food look like when you don't use food photography?&lt;/p&gt;

&lt;p&gt;That was the question behind &lt;strong&gt;Rasa&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rasa is an interactive culinary atlas exploring India through &lt;strong&gt;Shadrasa&lt;/strong&gt;, the six tastes described in Ayurveda:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Madhura&lt;/strong&gt; — sweet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amla&lt;/strong&gt; — sour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lavana&lt;/strong&gt; — salty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Katu&lt;/strong&gt; — pungent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tikta&lt;/strong&gt; — bitter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kashaya&lt;/strong&gt; — astringent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted to turn those six tastes into something visual rather than simply presenting them as text or cards.&lt;/p&gt;

&lt;p&gt;The result is a tongue-shaped interactive map.&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%2Frin2mx4qa5m9pq76wtni.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%2Frin2mx4qa5m9pq76wtni.png" alt="Six guided taste receptors are distributed across the tongue" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One important clarification: the familiar "tongue taste map" is scientifically inaccurate. Taste receptors are distributed across the tongue. In Rasa, the tongue is purely a &lt;strong&gt;cultural and artistic navigation metaphor&lt;/strong&gt;, not an anatomy diagram or medical claim.&lt;/p&gt;

&lt;p&gt;The idea was to make something that felt like opening an old food journal rather than browsing a database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CSS Art
&lt;/h2&gt;

&lt;p&gt;The tongue is probably the most obvious piece of CSS art in the project.&lt;/p&gt;

&lt;p&gt;It isn't an image.&lt;/p&gt;

&lt;p&gt;It is a layered CSS illustration with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Six selectable rasa regions&lt;/li&gt;
&lt;li&gt;A draggable, bounded CSS 3D orbit&lt;/li&gt;
&lt;li&gt;Pointer-responsive lighting&lt;/li&gt;
&lt;li&gt;Pan and zoom&lt;/li&gt;
&lt;li&gt;Keyboard orbit and navigation&lt;/li&gt;
&lt;li&gt;A dedicated control to centre the 3D view&lt;/li&gt;
&lt;li&gt;Papillae and surface texture&lt;/li&gt;
&lt;li&gt;A median sulcus&lt;/li&gt;
&lt;li&gt;Subtle veins and mucosal variation&lt;/li&gt;
&lt;li&gt;Rim lighting&lt;/li&gt;
&lt;li&gt;Subsurface colour&lt;/li&gt;
&lt;li&gt;Counter-scaled labels that remain readable while zooming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tongue can be explored almost like a little map.&lt;/p&gt;

&lt;p&gt;Select a rasa and the camera moves towards that region, revealing the corresponding field notes.&lt;/p&gt;

&lt;p&gt;The 3D effect does not use WebGL, canvas or a 3D library. JavaScript only captures pointer and keyboard input and updates a few CSS custom properties. Perspective, depth, lighting, counter-rotation and shadows remain CSS work.&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%2F1vof5ofgk44aykd3f8bn.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%2F1vof5ofgk44aykd3f8bn.png" alt="Nima, the bitterness guide" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That interaction was important to me.&lt;/p&gt;

&lt;p&gt;I didn't want the CSS art to be something you simply look at.&lt;/p&gt;

&lt;p&gt;I wanted it to become part of the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Food Without Food Images
&lt;/h2&gt;

&lt;p&gt;The other fun constraint was food itself.&lt;/p&gt;

&lt;p&gt;Instead of using food photography, every dish has a small declarative &lt;strong&gt;plate-art recipe&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%2F8a69r98ez652ynqr6wsi.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%2F8a69r98ez652ynqr6wsi.png" alt="Pulihora, a South Indian dish" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The renderer combines things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vessel&lt;/li&gt;
&lt;li&gt;Food form&lt;/li&gt;
&lt;li&gt;Colours&lt;/li&gt;
&lt;li&gt;Garnishes&lt;/li&gt;
&lt;li&gt;Texture&lt;/li&gt;
&lt;li&gt;Lighting&lt;/li&gt;
&lt;li&gt;Serving temperature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The renderer supports vessels including thalis, plates, bowls, kadhais, clay pots, banana leaves and leaf cones.&lt;/p&gt;

&lt;p&gt;Food forms include rounds, stacks, curries, grains, crescents, cones, cubes and strands.&lt;/p&gt;

&lt;p&gt;CSS then adds details such as oil sheen, highlights, contact shadows, steam, heat haze, condensation and cold mist.&lt;/p&gt;

&lt;p&gt;So a dish isn't a static illustration.&lt;/p&gt;

&lt;p&gt;It is data that becomes an illustration.&lt;/p&gt;

&lt;p&gt;That was one of my favourite parts of building Rasa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six Handcrafted Taste Guides
&lt;/h2&gt;

&lt;p&gt;Each rasa is accompanied by a guide with a distinct silhouette, costume, hairstyle, expression and role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guddi&lt;/strong&gt;, the Culinary Archivist for sweet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ami&lt;/strong&gt;, the Fermentation Botanist for sour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neer&lt;/strong&gt;, the Coastal Navigator for salty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tara&lt;/strong&gt;, the Spice Route Chronicler for pungent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nima&lt;/strong&gt;, the Ethnobotanical Elder for bitter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jamu&lt;/strong&gt;, the Tea and Tannin Cartographer for astringent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The characters are built from nested HTML and CSS rather than generated avatars, SVG artwork or image files. Gradients, borders, pseudo-elements, clipping and transforms create their faces, clothing, hair, accessories and tools.&lt;/p&gt;

&lt;p&gt;They can greet the visitor, scout ingredients and share lore. Their mouths respond while they speak, and gestures such as walking, waving and pointing are finite rather than perpetual. This keeps the characters playful without turning the page into a wall of constant motion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making CSS Feel Less Like CSS
&lt;/h2&gt;

&lt;p&gt;A lot of the work was experimenting with the things CSS is surprisingly good at:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gradients + shadows + transforms + masks + pseudo-elements + 3D transforms&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Individually, none of those are particularly exotic.&lt;/p&gt;

&lt;p&gt;Combined carefully, they can start to feel much closer to illustration.&lt;/p&gt;

&lt;p&gt;I also added a small layer of atmospheric "ingredient wind" across the site.&lt;/p&gt;

&lt;p&gt;Different rasas get their own ingredient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jaggery for sweet&lt;/li&gt;
&lt;li&gt;Mango leaf for sour&lt;/li&gt;
&lt;li&gt;Salt crystal for salty&lt;/li&gt;
&lt;li&gt;Chilli for pungent&lt;/li&gt;
&lt;li&gt;Neem leaf for bitter&lt;/li&gt;
&lt;li&gt;Jamun for astringent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ingredients drift across the page like leaves in a breeze.&lt;/p&gt;

&lt;p&gt;It is intentionally subtle.&lt;/p&gt;

&lt;p&gt;It can also be paused, disappears in forced-colour mode, and respects reduced-motion preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Art With a Purpose
&lt;/h2&gt;

&lt;p&gt;One thing I wanted to avoid was making a collection of pretty CSS experiments that didn't actually help the user.&lt;/p&gt;

&lt;p&gt;The visual language is tied to the content.&lt;/p&gt;

&lt;p&gt;The tongue represents the six tastes.&lt;/p&gt;

&lt;p&gt;The plate art represents the food.&lt;/p&gt;

&lt;p&gt;The notebook changes atmosphere depending on the dish.&lt;/p&gt;

&lt;p&gt;Hot dishes introduce warmth, steam and heat shimmer.&lt;/p&gt;

&lt;p&gt;Chilled dishes introduce condensation, frost and cool mist.&lt;/p&gt;

&lt;p&gt;Festival dishes receive restrained gold details.&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%2Fp94a5vew39p5tdy2jf2e.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%2Fp94a5vew39p5tdy2jf2e.png" alt="Festive dishes" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Street food gets a subtle field-stamp treatment.&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%2Ftnkzhulskvpuyppgy5o3.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%2Ftnkzhulskvpuyppgy5o3.png" alt="Street food dokhla" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The visual effects are therefore part of the storytelling rather than decoration added afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  84 Dishes Instead of Six
&lt;/h2&gt;

&lt;p&gt;The six tastes are only the entry point.&lt;/p&gt;

&lt;p&gt;Rasa contains &lt;strong&gt;84 dishes&lt;/strong&gt;, with 14 primary dishes for each rasa, covering all 28 Indian states and multiple Union Territories across eight culinary macro-regions.&lt;/p&gt;

&lt;p&gt;The dataset intentionally goes beyond the usual shorthand for Indian food.&lt;/p&gt;

&lt;p&gt;There are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyday meals&lt;/li&gt;
&lt;li&gt;Breakfasts&lt;/li&gt;
&lt;li&gt;Snacks&lt;/li&gt;
&lt;li&gt;Street foods&lt;/li&gt;
&lt;li&gt;Festival foods&lt;/li&gt;
&lt;li&gt;Temple foods&lt;/li&gt;
&lt;li&gt;City specialities&lt;/li&gt;
&lt;li&gt;Folk and Adivasi traditions&lt;/li&gt;
&lt;li&gt;Fermented foods&lt;/li&gt;
&lt;li&gt;Coastal cuisines&lt;/li&gt;
&lt;li&gt;Mountain cuisines&lt;/li&gt;
&lt;li&gt;Sweets&lt;/li&gt;
&lt;li&gt;Drinks&lt;/li&gt;
&lt;li&gt;Pickles&lt;/li&gt;
&lt;li&gt;Breads&lt;/li&gt;
&lt;li&gt;Rice dishes&lt;/li&gt;
&lt;li&gt;Curries&lt;/li&gt;
&lt;li&gt;Preserved foods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was breadth rather than stereotype.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Side
&lt;/h2&gt;

&lt;p&gt;Rasa is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18&lt;/li&gt;
&lt;li&gt;TypeScript 5.7&lt;/li&gt;
&lt;li&gt;Vite 6&lt;/li&gt;
&lt;li&gt;Tailwind CSS 4&lt;/li&gt;
&lt;li&gt;Lucide React&lt;/li&gt;
&lt;li&gt;Handcrafted CSS character art&lt;/li&gt;
&lt;li&gt;Playwright&lt;/li&gt;
&lt;li&gt;axe-core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application is data-driven.&lt;/p&gt;

&lt;p&gt;The six rasas, dishes, regions, festivals, spices and guides are represented as domain models rather than being embedded directly into components.&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%2F52pmxjxhhdw53r802aoj.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%2F52pmxjxhhdw53r802aoj.png" alt="Indian Regions" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That makes the CSS renderer reusable.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Dish&lt;/code&gt; can describe what something is, while its &lt;code&gt;PlateArt&lt;/code&gt; recipe describes how it should look.&lt;/p&gt;

&lt;p&gt;That separation made it much easier to keep adding dishes without creating a new component for every food.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessibility Still Applies to CSS Art
&lt;/h2&gt;

&lt;p&gt;One of the interesting challenges was making something visually experimental still behave like a normal application.&lt;/p&gt;

&lt;p&gt;The map is keyboard-operable.&lt;/p&gt;

&lt;p&gt;The rasa controls, field-note tabs, guide commands, notebook corners and CSS 3D orbit are keyboard-operable.&lt;/p&gt;

&lt;p&gt;Interactive controls have accessible names and 44 × 44px minimum targets.&lt;/p&gt;

&lt;p&gt;There is a skip link.&lt;/p&gt;

&lt;p&gt;Notebook page changes are announced to screen readers.&lt;/p&gt;

&lt;p&gt;The experience supports reduced motion, persistent motion controls, forced colours, 200% zoom, text-spacing overrides and 320px reflow. Native dish names carry language metadata, page and guide changes use polite status announcements, and textured-paper text now uses darker ink colours for enhanced contrast.&lt;/p&gt;

&lt;p&gt;I also added 13 Playwright accessibility and interaction journeys. They run axe with WCAG 2.0, 2.1 and 2.2 A, AA and AAA tags, exercise all six rasa states and all field-note tabs, and check keyboard selection, reduced motion, text spacing, target sizes, finite guide animation, CSS 3D keyboard orbit and notebook corner navigation.&lt;/p&gt;

&lt;p&gt;The latest automated run passes all 13 journeys with no axe violations or unresolved structural ARIA checks. Textured gradients can prevent automated tools from resolving every contrast pair, so the underlying ink-on-paper colour pairs were also reviewed directly. This still is not a claim that automation proves complete WCAG AAA conformance: manual testing remains necessary for reading order, language and pronunciation, cognitive clarity, and real assistive-technology behaviour.&lt;/p&gt;

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

&lt;p&gt;The biggest lesson was that CSS art becomes much more interesting when it has a job.&lt;/p&gt;

&lt;p&gt;It is tempting to ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How can I make this look cool with CSS?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question turned out to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What should this visual communicate?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the answer was clear, the implementation became much easier.&lt;/p&gt;

&lt;p&gt;The tongue needed depth because it was a map.&lt;/p&gt;

&lt;p&gt;The food needed texture because it represented serving and preparation.&lt;/p&gt;

&lt;p&gt;The notebook needed page turns because the content was designed as a field journal.&lt;/p&gt;

&lt;p&gt;The current notebook has clickable outer page corners as well as conventional Previous and Next controls. Each corner uses a small rounded CSS curl: the right edge lifts subtly upward and left, while the left edge lifts upward and right. The fold, underside, highlight and shadow are all CSS, while the button underneath remains semantic and keyboard accessible.&lt;/p&gt;

&lt;p&gt;The motion needed to communicate state instead of simply moving things around.&lt;/p&gt;

&lt;p&gt;That became one of the design principles for the entire project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Motion should explain state or create atmosphere, never prevent reading.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;🍛 &lt;strong&gt;Try Rasa:&lt;/strong&gt; &lt;a href="https://rasa-mu.vercel.app/" rel="noopener noreferrer"&gt;https://rasa-mu.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The source code is available on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ujjavala" rel="noopener noreferrer"&gt;
        ujjavala
      &lt;/a&gt; / &lt;a href="https://github.com/ujjavala/rasa" rel="noopener noreferrer"&gt;
        rasa
      &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;Rasa — The Tongue Map of India&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An interactive culinary atlas that explores India through &lt;strong&gt;Shadrasa&lt;/strong&gt;, Ayurveda's six tastes: sweet, sour, salty, pungent, bitter, and astringent.&lt;/p&gt;
&lt;p&gt;Rasa is a React experience built for the DEV Frontend Challenge themes &lt;strong&gt;CSS Art: Comfort Food&lt;/strong&gt; and &lt;strong&gt;Perfect Landing: Comfort Food&lt;/strong&gt;. It combines an editorial landing page, a zoomable CSS-art tongue map, character-led exploration, regional food history, and a page-turning field notebook containing 84 dishes from across India.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; the familiar “tongue taste map” is scientifically inaccurate. Taste receptors are distributed across the tongue. This project uses the tongue only as a cultural and artistic navigation metaphor for the six rasas; it is not an anatomy chart or medical guidance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this project exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Indian food is often flattened into a small set of restaurant dishes. Rasa was created to show a wider table: home cooking, street food, temple food, festival dishes…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ujjavala/rasa" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Youtube link: &lt;/p&gt;

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

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

&lt;p&gt;There are plenty of things I'd like to explore further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ingredient and state-based search&lt;/li&gt;
&lt;li&gt;Seasonal food trails&lt;/li&gt;
&lt;li&gt;Audio pronunciation for native dish names&lt;/li&gt;
&lt;li&gt;More community-reviewed cultural sources&lt;/li&gt;
&lt;li&gt;Shareable notebook pages&lt;/li&gt;
&lt;li&gt;Deeper VoiceOver, NVDA, TalkBack and keyboard-only testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, Rasa is my little experiment in asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How far can CSS go when you stop treating it as styling and start treating it as an illustration medium?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And perhaps more importantly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a website about comfort food make you hungry without using a single food photograph?&lt;/strong&gt; 🍚&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
      <category>react</category>
    </item>
    <item>
      <title>Designing a Landing Page That Feels Like Opening a Food Journal</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sat, 15 Aug 2026 03:16:15 +0000</pubDate>
      <link>https://dev.to/ujja/rasa-designing-a-landing-page-that-feels-like-opening-a-food-journal-mb9</link>
      <guid>https://dev.to/ujja/rasa-designing-a-landing-page-that-feels-like-opening-a-food-journal-mb9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition, Perfect Landing&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;Rasa&lt;/strong&gt;, an interactive culinary atlas that explores India through the six tastes of &lt;strong&gt;Shadrasa&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sweet, sour, salty, pungent, bitter and astringent.&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%2Fmvaaku7oppfe0wzfsw2o.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%2Fmvaaku7oppfe0wzfsw2o.png" alt="Image showing India is more than just butter chicken. Also has a rasa wheel on the right" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The starting point was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if a food atlas felt less like a database and more like opening a traveller's field journal?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That idea shaped the entire landing page.&lt;/p&gt;

&lt;p&gt;Instead of starting with a grid of dishes or a traditional restaurant-style hero, I wanted the page to feel editorial, tactile and slightly mysterious.&lt;/p&gt;

&lt;p&gt;The landing page introduces the six tastes through a CSS-built rasa mandala and then invites the visitor into the atlas.&lt;/p&gt;

&lt;p&gt;From there, the experience expands into an interactive tongue map, character-led exploration, regional food history and a page-turning culinary notebook containing 84 dishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landing Page Is the Beginning, Not the Destination
&lt;/h2&gt;

&lt;p&gt;I approached the hero as an invitation rather than a summary.&lt;/p&gt;

&lt;p&gt;The visitor first encounters the concept of Rasa.&lt;/p&gt;

&lt;p&gt;Then the six tastes.&lt;/p&gt;

&lt;p&gt;Then the tongue.&lt;/p&gt;

&lt;p&gt;Then the exploration.&lt;/p&gt;

&lt;p&gt;That progression was deliberate.&lt;/p&gt;

&lt;p&gt;I didn't want to put every feature into the first screen.&lt;/p&gt;

&lt;p&gt;The landing page establishes the mental model first:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rasa → taste → region → dish → story&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that relationship is understood, the rest of the application becomes easier to navigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Tongue?
&lt;/h2&gt;

&lt;p&gt;There is a familiar visual idea of dividing the tongue into taste regions.&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%2F6ep0vtpdeyupoo18ngdq.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%2F6ep0vtpdeyupoo18ngdq.png" alt="Has a 3D tongue on which personified taste guides stand" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Taste receptors are distributed across the tongue, so Rasa does &lt;strong&gt;not&lt;/strong&gt; present this as an anatomy chart.&lt;/p&gt;

&lt;p&gt;Instead, I used the tongue as a visual metaphor.&lt;/p&gt;

&lt;p&gt;Six regions.&lt;/p&gt;

&lt;p&gt;Six tastes.&lt;/p&gt;

&lt;p&gt;Six entry points into a much larger culinary landscape.&lt;/p&gt;

&lt;p&gt;The tongue is interactive, zoomable and keyboard accessible. It also supports a shallow, bounded 3D orbit: visitors can drag the backdrop, use arrow keys, hold Shift for larger steps, or press Home and the dedicated centre control to reset the view.&lt;/p&gt;

&lt;p&gt;Selecting a rasa moves the camera towards that region and opens its field notes.&lt;/p&gt;

&lt;p&gt;The depth effect is still CSS-first. JavaScript captures input and updates CSS custom properties; perspective, layer separation, lighting, shadows and character depth are rendered in CSS. There is no canvas, WebGL or 3D dependency.&lt;/p&gt;

&lt;p&gt;So the hero concept eventually becomes the primary navigation mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Landing Page to Field Journal
&lt;/h2&gt;

&lt;p&gt;The biggest design decision was to avoid making the experience feel like a food catalogue.&lt;/p&gt;

&lt;p&gt;India has far too many food traditions for that approach to feel right.&lt;/p&gt;

&lt;p&gt;Rasa explores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everyday home cooking&lt;/li&gt;
&lt;li&gt;Street food&lt;/li&gt;
&lt;li&gt;Festival and temple food&lt;/li&gt;
&lt;li&gt;City specialities&lt;/li&gt;
&lt;li&gt;Folk and Adivasi traditions&lt;/li&gt;
&lt;li&gt;Fermented foods&lt;/li&gt;
&lt;li&gt;Coastal cuisines&lt;/li&gt;
&lt;li&gt;Mountain cuisines&lt;/li&gt;
&lt;li&gt;Regional ingredients and techniques&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current dataset contains &lt;strong&gt;84 dishes&lt;/strong&gt; across eight culinary macro-regions, representing all 28 Indian states and multiple Union Territories.&lt;/p&gt;

&lt;p&gt;Each dish includes context around its history, ingredients, technique, occasions and regional identity.&lt;/p&gt;

&lt;p&gt;The landing page therefore needed to communicate &lt;strong&gt;exploration&lt;/strong&gt;, not simply consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Guides
&lt;/h2&gt;

&lt;p&gt;I also wanted the interface to have some personality.&lt;/p&gt;

&lt;p&gt;Each rasa has a guide with a distinct identity:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Taste&lt;/th&gt;
&lt;th&gt;Guide&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Madhura · sweet&lt;/td&gt;
&lt;td&gt;Guddi&lt;/td&gt;
&lt;td&gt;Culinary Archivist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amla · sour&lt;/td&gt;
&lt;td&gt;Ami&lt;/td&gt;
&lt;td&gt;Fermentation Botanist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lavana · salty&lt;/td&gt;
&lt;td&gt;Neer&lt;/td&gt;
&lt;td&gt;Coastal Navigator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Katu · pungent&lt;/td&gt;
&lt;td&gt;Tara&lt;/td&gt;
&lt;td&gt;Spice Route Chronicler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tikta · bitter&lt;/td&gt;
&lt;td&gt;Nima&lt;/td&gt;
&lt;td&gt;Ethnobotanical Elder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kashaya · astringent&lt;/td&gt;
&lt;td&gt;Jamu&lt;/td&gt;
&lt;td&gt;Tea and Tannin Cartographer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each guide can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Greet&lt;/li&gt;
&lt;li&gt;Scout ingredients&lt;/li&gt;
&lt;li&gt;Reveal lore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The portraits are handcrafted from nested HTML and CSS. Each guide has a different figure, skin tone, costume, hairstyle, accessories and expression, with Guddi, Tara and Nima represented as women and Ami, Neer and Jamu as men. They use no generated avatar package, SVG character asset or image file.&lt;/p&gt;

&lt;p&gt;Their walking, waving, scouting and lore gestures are intentionally finite rather than constant idle animation. Speech bubbles restart for each command, and the mouth moves briefly while a guide talks.&lt;/p&gt;

&lt;p&gt;That was another deliberate choice.&lt;/p&gt;

&lt;p&gt;I wanted characters to feel responsive rather than noisy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Notebook
&lt;/h2&gt;

&lt;p&gt;The deeper you go into the experience, the more the interface changes.&lt;/p&gt;

&lt;p&gt;Instead of a conventional card grid, dishes appear in a two-page field notebook.&lt;/p&gt;

&lt;p&gt;You can turn pages.&lt;/p&gt;

&lt;p&gt;You can also use the lifted outer corners: the right corner turns forward and the left corner turns backward. They are semantic buttons with subtle rounded CSS curls that rise inward like the edge of a real page, while the existing Previous, Next and direct-jump controls remain available.&lt;/p&gt;

&lt;p&gt;Jump directly to a dish.&lt;/p&gt;

&lt;p&gt;Move backwards and forwards.&lt;/p&gt;

&lt;p&gt;See regional and contextual annotations.&lt;/p&gt;

&lt;p&gt;Read the native name with appropriate language metadata.&lt;/p&gt;

&lt;p&gt;Follow the dish to further context.&lt;/p&gt;

&lt;p&gt;The notebook also changes atmosphere based on the food.&lt;/p&gt;

&lt;p&gt;A piping-hot dish can introduce warmth, steam and heat shimmer.&lt;/p&gt;

&lt;p&gt;A chilled dish can introduce frost, condensation and cool mist.&lt;/p&gt;

&lt;p&gt;Festival foods receive restrained gold detailing.&lt;/p&gt;

&lt;p&gt;Street foods receive a subtle field-stamp treatment.&lt;/p&gt;

&lt;p&gt;This was my way of making the interface feel like a physical object rather than another collection of UI cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing for Accessibility From the Start
&lt;/h2&gt;

&lt;p&gt;A visually heavy landing page can very quickly become inaccessible.&lt;/p&gt;

&lt;p&gt;So accessibility wasn't treated as something to check after the animations were finished.&lt;/p&gt;

&lt;p&gt;The application includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic landmarks and headings&lt;/li&gt;
&lt;li&gt;A skip link&lt;/li&gt;
&lt;li&gt;Keyboard-operable navigation&lt;/li&gt;
&lt;li&gt;Roving tab navigation&lt;/li&gt;
&lt;li&gt;Visible focus states&lt;/li&gt;
&lt;li&gt;Accessible icon controls&lt;/li&gt;
&lt;li&gt;44 × 44px minimum interactive targets&lt;/li&gt;
&lt;li&gt;Screen-reader announcements&lt;/li&gt;
&lt;li&gt;Reduced-motion support&lt;/li&gt;
&lt;li&gt;Persistent pause/play motion control&lt;/li&gt;
&lt;li&gt;Forced-colour support&lt;/li&gt;
&lt;li&gt;200% zoom support&lt;/li&gt;
&lt;li&gt;320px reflow&lt;/li&gt;
&lt;li&gt;Text-spacing resilience&lt;/li&gt;
&lt;li&gt;Keyboard control for the CSS 3D orbit and notebook corners&lt;/li&gt;
&lt;li&gt;Enhanced ink-on-paper contrast&lt;/li&gt;
&lt;li&gt;Automated axe checks through Playwright across all six rasa states and every field-note tab&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The experience should remain useful when the visual effects disappear.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone disables animation, uses keyboard navigation, zooms the page or doesn't have pointer input, the content and navigation still need to make sense.&lt;/p&gt;

&lt;p&gt;The current suite contains 13 accessibility and interaction journeys and passes in Chromium. It runs axe using WCAG 2.0, 2.1 and 2.2 A, AA and AAA tags, and now rejects unresolved structural ARIA findings as well as violations. Automation still cannot certify every AAA success criterion, especially where manual judgement, language, pronunciation, reading clarity or real assistive technology is involved, so manual review remains part of the roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motion With Meaning
&lt;/h2&gt;

&lt;p&gt;There is quite a lot of movement in Rasa.&lt;/p&gt;

&lt;p&gt;But every type of movement has a reason.&lt;/p&gt;

&lt;p&gt;Guide movement communicates an interaction.&lt;/p&gt;

&lt;p&gt;The camera movement communicates navigation.&lt;/p&gt;

&lt;p&gt;Notebook movement communicates page direction.&lt;/p&gt;

&lt;p&gt;Steam and heat haze communicate serving temperature.&lt;/p&gt;

&lt;p&gt;Ingredient wind creates atmosphere.&lt;/p&gt;

&lt;p&gt;And the global motion control lets the user pause the experience.&lt;/p&gt;

&lt;p&gt;The application also respects &lt;code&gt;prefers-reduced-motion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This led to one of my favourite principles from the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Movement should explain state or create atmosphere, never prevent reading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Visual Language
&lt;/h2&gt;

&lt;p&gt;I wanted the interface to feel somewhere between:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a field journal + an old map + a recipe notebook + a museum exhibit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Brass.&lt;/p&gt;

&lt;p&gt;Paper.&lt;/p&gt;

&lt;p&gt;Ink.&lt;/p&gt;

&lt;p&gt;Food vessels.&lt;/p&gt;

&lt;p&gt;Spice textures.&lt;/p&gt;

&lt;p&gt;Map controls.&lt;/p&gt;

&lt;p&gt;Shadows.&lt;/p&gt;

&lt;p&gt;Atmospheric movement.&lt;/p&gt;

&lt;p&gt;Most of those visual elements are created with CSS rather than remote food photography.&lt;/p&gt;

&lt;p&gt;That constraint ended up becoming one of the most interesting parts of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Stack
&lt;/h2&gt;

&lt;p&gt;The application is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18&lt;/li&gt;
&lt;li&gt;TypeScript 5.7 in strict mode&lt;/li&gt;
&lt;li&gt;Vite 6&lt;/li&gt;
&lt;li&gt;Tailwind CSS 4&lt;/li&gt;
&lt;li&gt;Lucide React&lt;/li&gt;
&lt;li&gt;Handcrafted HTML and CSS character art&lt;/li&gt;
&lt;li&gt;ESLint&lt;/li&gt;
&lt;li&gt;Stylelint&lt;/li&gt;
&lt;li&gt;Playwright&lt;/li&gt;
&lt;li&gt;axe-core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application is deliberately data-driven.&lt;/p&gt;

&lt;p&gt;Rasas, dishes, regions, festivals, spices and explorers are represented as data models.&lt;/p&gt;

&lt;p&gt;React components are responsible for rendering those models rather than containing the cultural content themselves.&lt;/p&gt;

&lt;p&gt;That made it possible to build a fairly elaborate experience without turning the components into giant collections of hard-coded content.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Enjoyed Most
&lt;/h2&gt;

&lt;p&gt;The most satisfying part was seeing a landing-page idea evolve into an actual world.&lt;/p&gt;

&lt;p&gt;At first it was just:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;six tastes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;six tastes → six regions → six guides.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;guides → dishes → regions → festivals → spices.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eventually it became an atlas containing 84 dishes and multiple ways to explore them.&lt;/p&gt;

&lt;p&gt;That progression made the project feel much more like a small product than a challenge submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Improve Next
&lt;/h2&gt;

&lt;p&gt;There is still plenty I would like to add.&lt;/p&gt;

&lt;p&gt;Some ideas are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search by ingredient, state, season and technique&lt;/li&gt;
&lt;li&gt;Audio pronunciation for native dish names&lt;/li&gt;
&lt;li&gt;User-created food trails&lt;/li&gt;
&lt;li&gt;Shareable notebook pages&lt;/li&gt;
&lt;li&gt;More community-reviewed citations and oral-history sources&lt;/li&gt;
&lt;li&gt;More manual accessibility testing with VoiceOver, NVDA and TalkBack&lt;/li&gt;
&lt;li&gt;Additional contrast review for complex textured and gradient surfaces&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;🍛 &lt;strong&gt;Explore Rasa:&lt;/strong&gt; &lt;a href="https://rasa-mu.vercel.app/" rel="noopener noreferrer"&gt;https://rasa-mu.vercel.app/&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The project is open source on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ujjavala" rel="noopener noreferrer"&gt;
        ujjavala
      &lt;/a&gt; / &lt;a href="https://github.com/ujjavala/rasa" rel="noopener noreferrer"&gt;
        rasa
      &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;Rasa — The Tongue Map of India&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An interactive culinary atlas that explores India through &lt;strong&gt;Shadrasa&lt;/strong&gt;, Ayurveda's six tastes: sweet, sour, salty, pungent, bitter, and astringent.&lt;/p&gt;
&lt;p&gt;Rasa is a React experience built for the DEV Frontend Challenge themes &lt;strong&gt;CSS Art: Comfort Food&lt;/strong&gt; and &lt;strong&gt;Perfect Landing: Comfort Food&lt;/strong&gt;. It combines an editorial landing page, a zoomable CSS-art tongue map, character-led exploration, regional food history, and a page-turning field notebook containing 84 dishes from across India.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; the familiar “tongue taste map” is scientifically inaccurate. Taste receptors are distributed across the tongue. This project uses the tongue only as a cultural and artistic navigation metaphor for the six rasas; it is not an anatomy chart or medical guidance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why this project exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Indian food is often flattened into a small set of restaurant dishes. Rasa was created to show a wider table: home cooking, street food, temple food, festival dishes…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ujjavala/rasa" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;I started with a comfort-food challenge.&lt;/p&gt;

&lt;p&gt;I ended up building a small interactive atlas.&lt;/p&gt;

&lt;p&gt;That was probably the most enjoyable part of the challenge.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How do I make a landing page about food?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I asked:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How would a food atlas feel if you could walk into it?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rasa is my attempt at that answer.&lt;/p&gt;

&lt;p&gt;A little paper.&lt;/p&gt;

&lt;p&gt;A little spice.&lt;/p&gt;

&lt;p&gt;A little CSS magic.&lt;/p&gt;

&lt;p&gt;And a very large table. 🍛&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>You Know What's Worse Than Not Being Able to Log In?</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Thu, 13 Aug 2026 12:38:44 +0000</pubDate>
      <link>https://dev.to/ujja/you-know-whats-worse-than-not-being-able-to-log-in-5379</link>
      <guid>https://dev.to/ujja/you-know-whats-worse-than-not-being-able-to-log-in-5379</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You Know What's Worse Than Not Being Able to Log In?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being told everything worked right up until you try to actually use your account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, that was a real bug.&lt;/p&gt;

&lt;p&gt;And, somehow, I ended up being pulled into another authentication mystery. At this point, I’m starting to think authentication bugs have a personal grudge against me. 😅&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/ujja/you-know-what-happens-when-theres-a-bug-in-authentication-absolutely-nothing-g0m"&gt;previous Smash Story&lt;/a&gt;, I wrote about a bug where users simply couldn't log in. This time, the problem was sneakier because most of the flow looked completely healthy. The user was approved, the background task ran, the email and SMS arrived, and Cognito had a user.&lt;/p&gt;

&lt;p&gt;Then the user actually tried to use their account.&lt;/p&gt;

&lt;p&gt;And everything fell apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Started With Two User Pools
&lt;/h2&gt;

&lt;p&gt;The authentication setup was fairly large and had evolved over time, so there wasn't one shiny User Pool doing everything.&lt;/p&gt;

&lt;p&gt;We had an older Cognito User Pool supporting existing authentication flows, including mobile-based signup, while a newer User Pool handled a newer flow where users received an email containing their PIN. Both pools were intentional because they supported different parts of the authentication journey.&lt;/p&gt;

&lt;p&gt;That wasn't the problem.&lt;/p&gt;

&lt;p&gt;The interesting part was that the application database had its own representation of a user, while Cognito had another. On top of that, some of the work connecting those two systems happened asynchronously.&lt;/p&gt;

&lt;p&gt;As long as everyone agreed about who the user was, nobody cared.&lt;/p&gt;

&lt;p&gt;The moment they disagreed, authentication became very interested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tiny Timing Window
&lt;/h2&gt;

&lt;p&gt;The problem appeared in the partner and dependant journey.&lt;/p&gt;

&lt;p&gt;A member could create a partner or dependant during signup or later from the member details area. A relevant non-member user would then approve the account, which scheduled an asynchronous task called &lt;code&gt;SendingEmailsAfterApprovalBot&lt;/code&gt; in a &lt;code&gt;TaskList&lt;/code&gt; database table.&lt;/p&gt;

&lt;p&gt;That task ran every 15 minutes, and once it executed, the partner or dependant received an email and SMS containing a code. They could then confirm their account, set a password and sign in.&lt;/p&gt;

&lt;p&gt;Sounds reasonable, right?&lt;/p&gt;

&lt;p&gt;Now imagine this.&lt;/p&gt;

&lt;p&gt;A partner gets approved at 10:01 AM, so the background task is scheduled and everyone moves on with their lives. At 10:05 AM, before the task has run, the partner changes their name.&lt;/p&gt;

&lt;p&gt;That tiny change matters because the username for partner and dependant accounts is derived from their details. The database now has the &lt;strong&gt;new username&lt;/strong&gt;, while Cognito may still know about the &lt;strong&gt;old username&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing has visibly broken yet.&lt;/p&gt;

&lt;p&gt;The task is still waiting patiently in the database, completely unaware that the identity it is about to process has changed underneath it.&lt;/p&gt;

&lt;p&gt;Then, 15 minutes later, it wakes up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cognito Says, "I've Seen This Email Before"
&lt;/h2&gt;

&lt;p&gt;The background task tries to create the Cognito user using the latest username stored in the database.&lt;/p&gt;

&lt;p&gt;Cognito rejects the creation because the email address is already associated with an existing user, but that user has a different username.&lt;/p&gt;

&lt;p&gt;And there is an important detail here: &lt;strong&gt;the Cognito username is immutable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So we weren't dealing with a simple "just update the username" situation. We had two systems describing the same person with different usernames, and Cognito was quite reasonably refusing to create another user with the same email.&lt;/p&gt;

&lt;p&gt;The database was saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is the current username for this person."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cognito was effectively saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I already know this email, and it belongs to someone with another username."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same human.&lt;/p&gt;

&lt;p&gt;Different digital identity.&lt;/p&gt;

&lt;p&gt;And that mismatch was enough to break the journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything Looked Fine... Until It Didn't
&lt;/h2&gt;

&lt;p&gt;What made this bug particularly sneaky was what happened next.&lt;/p&gt;

&lt;p&gt;The user could still receive the expected email and SMS. There was no obvious failure screaming for attention. The asynchronous task had run, notifications had gone out and Cognito already had a user associated with the email.&lt;/p&gt;

&lt;p&gt;So the user did what any reasonable person would do: they followed the instructions.&lt;/p&gt;

&lt;p&gt;They entered their code, tried to confirm their account and attempted to set their password.&lt;/p&gt;

&lt;p&gt;Except the identity represented in Cognito didn't match the latest identity represented in the database.&lt;/p&gt;

&lt;p&gt;The user wasn't looking at a database synchronisation problem. They weren't thinking about immutable Cognito usernames or asynchronous jobs. They had received a perfectly legitimate code and were simply trying to finish creating their account.&lt;/p&gt;

&lt;p&gt;From their perspective, the system had basically said:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Here's your code. Everything is ready!"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Actually... never mind."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was the bug we had to chase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Sentry
&lt;/h2&gt;

&lt;p&gt;By this point, we knew something was going wrong somewhere between the database, the background task and Cognito, but knowing &lt;em&gt;where&lt;/em&gt; it broke was another story.&lt;/p&gt;

&lt;p&gt;This is where Sentry became extremely useful. The error context gave us the details around the Cognito failure, while the breadcrumbs helped reconstruct the events leading up to it. Instead of seeing a Cognito error in isolation and assuming we simply had a duplicate-user problem, we could look at what happened before the failure and connect it back to the user's journey.&lt;/p&gt;

&lt;p&gt;The important clue was that Cognito was rejecting the user creation because the email was already associated with an existing user. That told us &lt;em&gt;what&lt;/em&gt; Cognito didn't like, but the surrounding context helped explain &lt;em&gt;why&lt;/em&gt; we had reached that point in the first place.&lt;/p&gt;

&lt;p&gt;We could see the relevant user and operation context around the failure, follow the activity leading up to the Cognito call, and use the correlation ID to connect the same operation across services and logs. That made it much easier to piece together the timeline rather than chasing individual log entries and hoping they belonged to the same request.&lt;/p&gt;

&lt;p&gt;And once the timeline was visible, the problem suddenly made sense:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;approval → delayed task → name changed → username changed → Cognito already has the email → creation rejected → identities drift apart → confirmation fails&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was the moment the bug stopped looking mysterious.&lt;/p&gt;

&lt;p&gt;The code told us what &lt;em&gt;should&lt;/em&gt; happen. Sentry's error context, breadcrumbs and correlated logs helped us see what &lt;em&gt;actually&lt;/em&gt; happened.&lt;/p&gt;

&lt;p&gt;And honestly, that's one of the best parts of having good observability during an authentication investigation. The error tells you where the system complained; the breadcrumbs help you understand the story that led it there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix Was Simpler Than the Investigation
&lt;/h2&gt;

&lt;p&gt;Once we understood the problem, the fix was surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;We already checked whether a Cognito user existed for the email, but we were treating that as enough. Instead, we changed the logic to ask: &lt;em&gt;"Does the correct Cognito user for the current database record exist?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the email existed in Cognito but belonged to an outdated username, we deleted that user and recreated it with the latest username from the database. Since Cognito usernames are immutable, recreating the user was necessary to bring the two systems back into alignment.&lt;/p&gt;

&lt;p&gt;The important change wasn't the delete-and-recreate itself. It was recognising that &lt;strong&gt;the database was the source of truth for the current username&lt;/strong&gt;, while Cognito could still be holding an older representation of the same person.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weird Scenario Was the Important One
&lt;/h2&gt;

&lt;p&gt;The happy path already worked, so the interesting tests were the ones where state changed between steps.&lt;/p&gt;

&lt;p&gt;We tested the normal signup flows, partner and dependant creation, existing Cognito users, missing Cognito users and, most importantly, the scenario that exposed the bug: a partner or dependant was approved, changed their name before &lt;code&gt;SendingEmailsAfterApprovalBot&lt;/code&gt; ran, and then the background process tried to create their Cognito user using the latest database state.&lt;/p&gt;

&lt;p&gt;We then verified the part that actually mattered: could they receive their PIN, confirm their account, set their password and successfully sign in?&lt;/p&gt;

&lt;p&gt;Because nobody cares about our beautifully synchronised identity records. They just want to get into their account.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interesting Part Wasn't Actually Cognito
&lt;/h2&gt;

&lt;p&gt;Looking back, this wasn't really a Cognito bug. The database wasn't broken, and neither was the scheduled task. Each component was behaving according to its own rules.&lt;/p&gt;

&lt;p&gt;The problem was the assumption connecting them: that the identity we started processing would still be the identity we needed when the asynchronous process eventually ran.&lt;/p&gt;

&lt;p&gt;Then someone changed their name.&lt;/p&gt;

&lt;p&gt;That small change was enough to make the database username different from the immutable username already held by Cognito. Since the email was already taken, Cognito rejected the new user creation and the two systems drifted apart.&lt;/p&gt;

&lt;p&gt;That's the lesson I took from it: whenever identity exists in multiple systems, you need to be explicit about &lt;strong&gt;which system is the source of truth&lt;/strong&gt; and what happens when those representations disagree.&lt;/p&gt;

&lt;p&gt;Because eventually, they will.&lt;/p&gt;

&lt;p&gt;Someone will change their name. A background job will run later. A legacy system will already have a record. And Cognito will quite reasonably tell you that it has seen that email before.&lt;/p&gt;

&lt;h2&gt;
  
  
  And That's What Made This Bug So Sneaky
&lt;/h2&gt;

&lt;p&gt;The approval worked, the task ran, the notification was sent, Cognito had a user and the user followed the instructions. Yet somewhere between those perfectly reasonable steps, the identity had drifted.&lt;/p&gt;

&lt;p&gt;From the user's perspective, none of the underlying details mattered. They didn't know there were two User Pools, that the old pool handled mobile signup while the newer one sent the email PIN, or that a background task ran every 15 minutes.&lt;/p&gt;

&lt;p&gt;They simply knew they had done everything they were asked to do and still couldn't get into their account.&lt;/p&gt;

&lt;p&gt;Which brings me back to the opening question.&lt;/p&gt;

&lt;p&gt;What's worse than not being able to log in?&lt;/p&gt;

&lt;p&gt;Being told everything worked right up until you try to actually use your account.&lt;/p&gt;

&lt;p&gt;At least a failed login is honest. This bug was more like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Congratulations! Everything worked!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Really? Because I'm still locked out."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And that's probably the biggest lesson I took away from it: when debugging authentication, don't just look at the login screen. Follow the identity through the entire system, use your observability tools to reconstruct what actually happened, and find the moment when the different pieces stopped agreeing about who that user was.&lt;/p&gt;

&lt;p&gt;Because sometimes the authentication bug isn't hiding at login.&lt;/p&gt;

&lt;p&gt;Sometimes it's hiding in a background job, quietly waiting for someone to change their name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth strikes again. 🔐🐛&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>distributedsystems</category>
      <category>webdev</category>
    </item>
    <item>
      <title>You Know What Happens When There's a Bug in Authentication? Absolutely Nothing.</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Thu, 30 Jul 2026 14:23:19 +0000</pubDate>
      <link>https://dev.to/ujja/you-know-what-happens-when-theres-a-bug-in-authentication-absolutely-nothing-g0m</link>
      <guid>https://dev.to/ujja/you-know-what-happens-when-theres-a-bug-in-authentication-absolutely-nothing-g0m</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything Stops
&lt;/h2&gt;

&lt;p&gt;You know what happens when there's a bug in authentication?&lt;/p&gt;

&lt;p&gt;Nothing.&lt;/p&gt;

&lt;p&gt;Seriously... absolutely nothing.&lt;/p&gt;

&lt;p&gt;Not because nothing is broken, but because users simply can't get past the front door. They can't log in, which means they can't access the application, complete a booking, view their information, or do whatever they came there to do in the first place.&lt;/p&gt;

&lt;p&gt;Normally that's annoying.&lt;/p&gt;

&lt;p&gt;But what if the application isn't just another website?&lt;/p&gt;

&lt;p&gt;At the time, I was part of the authentication engineering team responsible for rolling out Multi-Factor Authentication (MFA) for government service centres across the state. These weren't internal dashboards or social media apps. These were systems used every day by frontline staff helping thousands of citizens.&lt;/p&gt;

&lt;p&gt;No login meant staff couldn't process driver's licence applications or renewals. They couldn't register births, deaths or marriages. They couldn't update identity records. During emergencies, they couldn't access systems used to support disaster relief efforts.&lt;/p&gt;

&lt;p&gt;The application itself was perfectly healthy. The backend was running. APIs were responding. Databases were online.&lt;/p&gt;

&lt;p&gt;But none of that mattered.&lt;/p&gt;

&lt;p&gt;Authentication is the front door. If the front door doesn't open, it doesn't matter how well the rest of the house is built.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Rollout We Thought Was Perfect
&lt;/h2&gt;

&lt;p&gt;Because authentication is so critical, we treated the rollout with the level of care you'd expect.&lt;/p&gt;

&lt;p&gt;We had unit tests, integration tests, end-to-end tests, security reviews and user acceptance testing. We tested happy paths, failure scenarios, edge cases and everything in between.&lt;/p&gt;

&lt;p&gt;Everything passed.&lt;/p&gt;

&lt;p&gt;The rollout went live.&lt;/p&gt;

&lt;p&gt;For a while, everything looked great.&lt;/p&gt;

&lt;p&gt;Then a few support tickets started appearing.&lt;/p&gt;

&lt;p&gt;Some users couldn't log in.&lt;/p&gt;

&lt;p&gt;The strange part was that it only affected a handful of people. Most users authenticated successfully, while a small group consistently received a server error.&lt;/p&gt;

&lt;p&gt;Those are the bugs every engineer dreads.&lt;/p&gt;

&lt;p&gt;If nobody can log in, you usually know where to start.&lt;/p&gt;

&lt;p&gt;If only &lt;em&gt;some&lt;/em&gt; users can't log in, every assumption suddenly becomes a possibility.&lt;/p&gt;

&lt;p&gt;Was it their browser?&lt;/p&gt;

&lt;p&gt;Their location?&lt;/p&gt;

&lt;p&gt;Their account?&lt;/p&gt;

&lt;p&gt;A feature flag?&lt;/p&gt;

&lt;p&gt;Some weird cached session?&lt;/p&gt;

&lt;p&gt;We checked everything.&lt;/p&gt;

&lt;p&gt;We dug through authentication logs, replayed requests, inspected network traffic, reviewed configuration, and read the login code more times than I'd like to admit.&lt;/p&gt;

&lt;p&gt;Everything looked correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop Looking at Logs. Watch People.
&lt;/h2&gt;

&lt;p&gt;Eventually, our team decided to do something different.&lt;/p&gt;

&lt;p&gt;Instead of staring at dashboards and log files, we visited one of the service centres and watched people use the application.&lt;/p&gt;

&lt;p&gt;Within minutes, we noticed something.&lt;/p&gt;

&lt;p&gt;The users clicking the &lt;strong&gt;Login&lt;/strong&gt; button with their mouse authenticated successfully every single time.&lt;/p&gt;

&lt;p&gt;The users navigating with the keyboard, pressing &lt;strong&gt;Tab&lt;/strong&gt; and then &lt;strong&gt;Enter&lt;/strong&gt;, consistently hit the server error.&lt;/p&gt;

&lt;p&gt;That tiny observation immediately felt important.&lt;/p&gt;

&lt;p&gt;As the feature lead developer for the MFA rollout, something clicked.&lt;/p&gt;

&lt;p&gt;This wasn't random.&lt;/p&gt;

&lt;p&gt;This wasn't infrastructure.&lt;/p&gt;

&lt;p&gt;This wasn't the backend.&lt;/p&gt;

&lt;p&gt;It had to be the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Smallest Bug with the Biggest Impact
&lt;/h2&gt;

&lt;p&gt;The culprit?&lt;/p&gt;

&lt;p&gt;One missing line.&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For anyone unfamiliar with it, &lt;code&gt;e.preventDefault()&lt;/code&gt; prevents the browser from performing its default action.&lt;/p&gt;

&lt;p&gt;In a React Single Page Application, forgetting it during form submission means the browser performs a traditional form submission instead of letting React handle the authentication flow.&lt;/p&gt;

&lt;p&gt;Our React application was being bypassed completely.&lt;/p&gt;

&lt;p&gt;Instead of executing our login logic, the browser submitted the form directly to the backend.&lt;/p&gt;

&lt;p&gt;Mouse clicks happened to work through our React event handler.&lt;/p&gt;

&lt;p&gt;Keyboard submissions didn't.&lt;/p&gt;

&lt;p&gt;Every automated test had passed.&lt;/p&gt;

&lt;p&gt;Every security review had passed.&lt;/p&gt;

&lt;p&gt;And yet government service centre staff couldn't access the systems they needed simply because one line of code was missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson Wasn't About &lt;code&gt;e.preventDefault()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I'm proud that once I saw someone press &lt;strong&gt;Enter&lt;/strong&gt;, I immediately suspected browser default behaviour and tracked the problem back to the missing &lt;code&gt;e.preventDefault()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But honestly, that wasn't the biggest lesson.&lt;/p&gt;

&lt;p&gt;The biggest lesson was about users.&lt;/p&gt;

&lt;p&gt;We spend a lot of time talking about AI-assisted development, automated testing and improving engineering productivity.&lt;/p&gt;

&lt;p&gt;I'm a huge believer in all of them.&lt;/p&gt;

&lt;p&gt;AI helps me write better code, generate tests, review pull requests and explore solutions much faster than I could a few years ago.&lt;/p&gt;

&lt;p&gt;But AI only validates the scenarios we think to test.&lt;/p&gt;

&lt;p&gt;Real users don't.&lt;/p&gt;

&lt;p&gt;Some never touch a mouse.&lt;/p&gt;

&lt;p&gt;Some navigate entirely with a keyboard.&lt;/p&gt;

&lt;p&gt;Some use assistive technologies.&lt;/p&gt;

&lt;p&gt;Some interact with software in ways we'd never think of while sitting at our desks.&lt;/p&gt;

&lt;p&gt;No amount of automation can replace watching someone actually use your application.&lt;/p&gt;

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

&lt;p&gt;Whenever people ask me about memorable production bugs, I don't think about distributed systems failures or obscure race conditions.&lt;/p&gt;

&lt;p&gt;I think about a missing &lt;code&gt;e.preventDefault()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One line of code.&lt;/p&gt;

&lt;p&gt;A rollout that had passed every test.&lt;/p&gt;

&lt;p&gt;And government service centres where staff couldn't help citizens because someone pressed &lt;strong&gt;Enter&lt;/strong&gt; instead of clicking a button.&lt;/p&gt;

&lt;p&gt;Sometimes the smallest bugs have the biggest impact.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Passion Atlas: A Living Map of Human Curiosity</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sun, 12 Jul 2026 15:32:47 +0000</pubDate>
      <link>https://dev.to/ujja/passion-atlas-a-living-map-of-human-curiosity-298h</link>
      <guid>https://dev.to/ujja/passion-atlas-a-living-map-of-human-curiosity-298h</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 built &lt;strong&gt;Passion Atlas&lt;/strong&gt; — an AI-powered map of human curiosity that helps people discover how their interests, experiences, cultures, and ideas connect.&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%2Fhomudh9u0tty214chkuy.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%2Fhomudh9u0tty214chkuy.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea started from something I noticed about myself: I have never had a single passion.&lt;/p&gt;

&lt;p&gt;Throughout my life, my interests kept evolving — from anime to dance, boxing to travel, food, culture, and understanding how people live around the world. For a long time, I thought this meant I lacked focus.&lt;/p&gt;

&lt;p&gt;But I eventually realised my interests were not random; they were connected.&lt;/p&gt;

&lt;p&gt;Anime introduced me to storytelling and different cultures. Dance connected me to movement and expression. Boxing taught me discipline and human psychology. Travel connected everything through food, history, traditions, and people's stories.&lt;/p&gt;

&lt;p&gt;Maybe humans are not designed around one fixed passion. Maybe curiosity itself is the passion.&lt;/p&gt;

&lt;p&gt;Our minds naturally explore, combine, and connect ideas. The most meaningful discoveries often happen when seemingly unrelated interests come together.&lt;/p&gt;

&lt;p&gt;Passion Atlas was built around this idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your interests are not separate islands. They are a connected universe.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Passion Atlas
&lt;/h2&gt;

&lt;p&gt;Passion Atlas creates a personal &lt;strong&gt;Passion Genome&lt;/strong&gt; — a living representation of someone's curiosity.&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%2Fj6h6qv5rfblshm7wdn0c.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%2Fj6h6qv5rfblshm7wdn0c.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;"What is your passion?"&lt;/p&gt;

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

&lt;p&gt;"What are all the things that make you curious, and how do they connect?"&lt;/p&gt;

&lt;p&gt;A user might enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Travel
Food
Photography
History
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead of generating a generic recommendation list, Passion Atlas creates connections:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Travel

↓

Cultural Exchange

↓

Local Food Traditions

↓

Storytelling

↓

Heritage Preservation

↓

Traditional Crafts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The goal is not to tell people what they should like.&lt;/p&gt;

&lt;p&gt;The goal is to reveal connections they never noticed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Passion Genome: Your Curiosity Fingerprint
&lt;/h2&gt;

&lt;p&gt;Every person has a unique combination of experiences.&lt;/p&gt;

&lt;p&gt;Two people can love the same thing but have completely different curiosity paths.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Person A:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Photography
+
Travel
+
History

=

Cultural Storytelling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Person B:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Photography
+
Nature
+
Science

=

Environmental Documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The same interest can evolve into completely different worlds.&lt;/p&gt;

&lt;p&gt;Passion Atlas captures these unique intersections.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Passion Butterfly Effect
&lt;/h2&gt;

&lt;p&gt;Small moments can create unexpected journeys.&lt;/p&gt;

&lt;p&gt;A single experience:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I attended a traditional tea ceremony in Japan."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Could become:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tea Culture

↓

Japanese Aesthetics

↓

Ceramics

↓

Traditional Crafts

↓

Sustainable Design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A small spark creates a larger curiosity path.&lt;/p&gt;

&lt;p&gt;Passion Atlas helps reveal these hidden chains.&lt;/p&gt;
&lt;h2&gt;
  
  
  Curiosity Wormholes
&lt;/h2&gt;

&lt;p&gt;Most recommendation systems optimise for similarity.&lt;/p&gt;

&lt;p&gt;Passion Atlas optimises for meaningful surprise.&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%2Fgd3w3vrzxeuiako6xka6.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%2Fgd3w3vrzxeuiako6xka6.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;"People who like hiking also like camping."&lt;/p&gt;

&lt;p&gt;It explores:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hiking

↓

Mountain Ecosystems

↓

Indigenous Knowledge

↓

Ancient Navigation

↓

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

&lt;/div&gt;


&lt;p&gt;Because sometimes the best discovery is something you were never searching for.&lt;/p&gt;
&lt;h2&gt;
  
  
  Preserving Human Stories
&lt;/h2&gt;

&lt;p&gt;Passion is not only about consuming knowledge.&lt;/p&gt;

&lt;p&gt;It is also about preserving it.&lt;/p&gt;

&lt;p&gt;Across the world, thousands of traditions, recipes, crafts, and personal stories risk disappearing.&lt;/p&gt;

&lt;p&gt;Passion Atlas creates &lt;strong&gt;Passion Time Capsules&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%2Ff5unobk9mah6ec9qwx7h.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%2Ff5unobk9mah6ec9qwx7h.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;People can contribute:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;personal stories&lt;/li&gt;
&lt;li&gt;cultural memories&lt;/li&gt;
&lt;li&gt;recipes&lt;/li&gt;
&lt;li&gt;artisan knowledge&lt;/li&gt;
&lt;li&gt;images&lt;/li&gt;
&lt;li&gt;audio recordings&lt;/li&gt;
&lt;li&gt;videos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI transforms these into connected cultural experiences.&lt;/p&gt;

&lt;p&gt;A grandmother's recipe is not just a recipe.&lt;/p&gt;

&lt;p&gt;It is connected to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;migration&lt;/li&gt;
&lt;li&gt;family history&lt;/li&gt;
&lt;li&gt;geography&lt;/li&gt;
&lt;li&gt;tradition&lt;/li&gt;
&lt;li&gt;identity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every story becomes part of the global curiosity map.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Passion Atlas is an AI-native application built using specialised agents.&lt;/p&gt;
&lt;h3&gt;
  
  
  Passion Extraction Agent
&lt;/h3&gt;

&lt;p&gt;Transforms human stories into structured curiosity data.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;interests&lt;/li&gt;
&lt;li&gt;emotions&lt;/li&gt;
&lt;li&gt;motivations&lt;/li&gt;
&lt;li&gt;cultural context&lt;/li&gt;
&lt;li&gt;related concepts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;Input:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I love making traditional food with my grandmother."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cooking

Family Heritage

Traditional Knowledge

Cultural Preservation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Curiosity Graph Agent
&lt;/h3&gt;

&lt;p&gt;Builds relationships between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;passions&lt;/li&gt;
&lt;li&gt;stories&lt;/li&gt;
&lt;li&gt;locations&lt;/li&gt;
&lt;li&gt;cultures&lt;/li&gt;
&lt;li&gt;experiences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The graph continuously evolves as more people contribute.&lt;/p&gt;
&lt;h3&gt;
  
  
  Discovery Agent
&lt;/h3&gt;

&lt;p&gt;Finds unexpected connections.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;novelty&lt;/li&gt;
&lt;li&gt;relevance&lt;/li&gt;
&lt;li&gt;emotional connection&lt;/li&gt;
&lt;li&gt;cultural depth&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Cultural Preservation Agent
&lt;/h3&gt;

&lt;p&gt;Transforms human experiences into meaningful knowledge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;summaries&lt;/li&gt;
&lt;li&gt;translations&lt;/li&gt;
&lt;li&gt;context&lt;/li&gt;
&lt;li&gt;connected discovery paths&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%2Fx7gukm0vetes211d0ytg.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%2Fx7gukm0vetes211d0ytg.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;p&gt;The app currently runs on local as it needs environment values. However, a simulation version is deployed on Vercel.&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://passion-atlas.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;passion-atlas.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&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/ujjavala" rel="noopener noreferrer"&gt;
        ujjavala
      &lt;/a&gt; / &lt;a href="https://github.com/ujjavala/passion-atlas" rel="noopener noreferrer"&gt;
        passion-atlas
      &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;🌌 Passion Atlas&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A living map of human curiosity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Passion Atlas is an AI-powered map that helps people discover how their interests, experiences, cultures, and ideas connect. Instead of asking &lt;em&gt;"What is your passion?"&lt;/em&gt;, it asks &lt;em&gt;"What are all the things that make you curious — and how do they connect?"&lt;/em&gt; and renders the answer as a personal &lt;strong&gt;Passion Genome&lt;/strong&gt;: a constellation of your curiosity where unexpected bridges between seemingly unrelated interests light up.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your interests are not separate islands. They are a connected universe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/weekend-2026-07-09" rel="nofollow"&gt;dev.to Weekend Challenge: Passion Edition&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Features&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Each screen is a different lens on the same living curiosity graph:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Screen&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;Atlas&lt;/strong&gt; (Canvas)&lt;/td&gt;
&lt;td&gt;Enter a passion and watch it expand into a constellation. Drill into any node to discover its connections; claim ownership of a discovery path on-chain.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🧬 &lt;strong&gt;Genome&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Your personal "Passion Genome" — the&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/ujjavala/passion-atlas" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

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

&lt;p&gt;Google Gemini powers the reasoning layer.&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%2F7w03mzeisyjfdgw2vgj9.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%2F7w03mzeisyjfdgw2vgj9.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;extract hidden passions from stories&lt;/li&gt;
&lt;li&gt;understand semantic relationships&lt;/li&gt;
&lt;li&gt;generate Passion Genomes&lt;/li&gt;
&lt;li&gt;discover unexpected connections&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%2Fm9f0sxupm3voc9j2vbnv.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%2Fm9f0sxupm3voc9j2vbnv.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gemini enables the system to reason across thousands of possible curiosity pathways.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏆 Best Use of Snowflake
&lt;/h3&gt;

&lt;p&gt;Snowflake powers the global Passion Graph.&lt;/p&gt;

&lt;p&gt;It stores and analyses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;passion relationships&lt;/li&gt;
&lt;li&gt;cultural stories&lt;/li&gt;
&lt;li&gt;discovery patterns&lt;/li&gt;
&lt;li&gt;human curiosity trends&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%2F4ee13jadcliywmkdevli.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%2F4ee13jadcliywmkdevli.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over time, the system can uncover patterns like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"People interested in ceramics often explore gardening, architecture, and sustainable design."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The graph becomes richer with every contribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏆 Best Use of ElevenLabs
&lt;/h3&gt;

&lt;p&gt;Passion is emotional.&lt;/p&gt;

&lt;p&gt;A story is not always the same when read as text.&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%2Fn67x8s9m3gg2zlwzdlgy.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%2Fn67x8s9m3gg2zlwzdlgy.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ElevenLabs enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;voice-based memories&lt;/li&gt;
&lt;li&gt;multilingual storytelling&lt;/li&gt;
&lt;li&gt;emotional narration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A craftsperson can share their knowledge in their own voice.&lt;/p&gt;

&lt;p&gt;A family story can remain human.&lt;/p&gt;

&lt;h3&gt;
  
  
  🏆 Best Use of Solana
&lt;/h3&gt;

&lt;p&gt;Passion Atlas introduces a contribution layer.&lt;/p&gt;

&lt;p&gt;Human knowledge creates value, but contributors are often invisible.&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%2F2ojinfc7776ouoh3j0nx.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%2F2ojinfc7776ouoh3j0nx.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Solana enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transparent recognition&lt;/li&gt;
&lt;li&gt;contributor rewards&lt;/li&gt;
&lt;li&gt;community incentives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional Weaving Story

↓

500 people discovered this tradition

↓

Contributor recognised

↓

Community preservation supported
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to financialise every passion.&lt;/p&gt;

&lt;p&gt;The goal is to recognise people who preserve and share human knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Passion Atlas?
&lt;/h2&gt;

&lt;p&gt;The internet already connects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;people&lt;/li&gt;
&lt;li&gt;places&lt;/li&gt;
&lt;li&gt;products&lt;/li&gt;
&lt;li&gt;information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it does not connect something deeply human:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;curiosity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maybe having many passions is not a lack of direction.&lt;/p&gt;

&lt;p&gt;Maybe it is a different way of exploring the world.&lt;/p&gt;

&lt;p&gt;Our interests are not random dots.&lt;/p&gt;

&lt;p&gt;They are a map.&lt;/p&gt;

&lt;p&gt;Passion Atlas helps us discover the connections.&lt;/p&gt;

&lt;p&gt;Because every passion leads somewhere.&lt;/p&gt;

&lt;p&gt;And every person carries a universe of curiosity waiting to be explored.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>gemini</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built a Turing inspired Treasure-Run Game Where Australia Saves the Sun</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Wed, 10 Jun 2026 08:41:06 +0000</pubDate>
      <link>https://dev.to/ujja/how-i-built-a-treasure-run-game-where-australia-saves-the-sun-kck</link>
      <guid>https://dev.to/ujja/how-i-built-a-treasure-run-game-where-australia-saves-the-sun-kck</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;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For this game jam, I built &lt;strong&gt;Dawn Dashers&lt;/strong&gt;, an adventure runner set during the &lt;strong&gt;June Solstice&lt;/strong&gt;, the longest night of the year in Australia.&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%2F16y6v7b129gqa9ah1wfh.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%2F16y6v7b129gqa9ah1wfh.png" alt="Dawn Dashers" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea started with a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if the longest night didn't end?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Dawn Dashers, a rogue machine called the &lt;strong&gt;Turing Engine&lt;/strong&gt; has stolen the sunrise and scattered seven &lt;strong&gt;Sun Fragments&lt;/strong&gt; across Australia. Players race through deserts, bushland trails, coastal cliffs, and hidden ruins to recover the fragments and bring daylight back.&lt;/p&gt;

&lt;p&gt;The game takes inspiration from the June solstice, but with an Australian treasure-hunting twist. Along the way, players dodge obstacles, collect relics, unlock animal abilities, and solve quick logic puzzles inspired by Alan Turing.&lt;/p&gt;

&lt;p&gt;One thing I wanted to avoid was making the solstice just part of the story. Instead, it's part of the gameplay itself. At the start, the world is trapped in darkness. As players recover Sun Fragments, the environment gradually becomes brighter, warmer, and more alive until the final sunrise returns.&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build something fun first, while using the June Solstice as the heart of the adventure.&lt;/strong&gt;&lt;/p&gt;

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

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

&lt;p&gt;You can also try: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running through the Australian Outback&lt;/li&gt;
&lt;li&gt;Discovering treasure clues&lt;/li&gt;
&lt;li&gt;Solving Turing-inspired logic challenges&lt;/li&gt;
&lt;li&gt;Collecting Sun Fragments&lt;/li&gt;
&lt;li&gt;Unlocking animal abilities&lt;/li&gt;
&lt;li&gt;Watching the world transform from the longest night back into daylight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... directly through the deployed game (using Vercel)  &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://dawn-dashers.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;dawn-dashers.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;&lt;a href="https://github.com/ujjavala/dawn-dashers" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vanilla JavaScript — game.js is the entire game engine (~6500+ lines)&lt;/li&gt;
&lt;li&gt;Three.js — 3D terrain rendering behind the 2D gameplay HUD&lt;/li&gt;
&lt;li&gt;Web Audio API&lt;/li&gt;
&lt;li&gt;HTML/CSS — index.html, inline UI modals, HUD&lt;/li&gt;
&lt;li&gt;GitHub Copilot&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I started by focusing on the core gameplay loop:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run → Dodge → Collect → Discover → Restore Light&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once movement felt fun, I started layering in everything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the Solstice Matter
&lt;/h3&gt;

&lt;p&gt;The biggest design decision was making the June Solstice an actual game mechanic rather than just background lore.&lt;/p&gt;

&lt;p&gt;The game begins during the longest night of the year. Every Sun Fragment recovered pushes the world closer to dawn.&lt;/p&gt;

&lt;p&gt;As players progress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Darkness slowly recedes&lt;/li&gt;
&lt;li&gt;New areas become visible&lt;/li&gt;
&lt;li&gt;Lighting becomes warmer&lt;/li&gt;
&lt;li&gt;The world feels more hopeful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of the game, the player has literally restored the sunrise.&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%2Fz8whca9yjdou397ijbrs.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%2Fz8whca9yjdou397ijbrs.png" alt="sunrise" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an Australian Adventure
&lt;/h3&gt;

&lt;p&gt;Rather than using generic fantasy locations, I wanted the game to feel distinctly Australian.&lt;/p&gt;

&lt;p&gt;The adventure takes players through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Outback ruins&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.amazonaws.com%2Fuploads%2Farticles%2Fc5eb17t5589234ik89b6.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%2Fc5eb17t5589234ik89b6.png" alt="Outback ruins" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bushland trails&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.amazonaws.com%2Fuploads%2Farticles%2Fef58l291wdj82rhb9he7.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%2Fef58l291wdj82rhb9he7.png" alt="Bushland trails" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A quirky roadside servo&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.amazonaws.com%2Fuploads%2Farticles%2Fukoa6ijxa1dqa06nu2r5.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%2Fukoa6ijxa1dqa06nu2r5.png" alt="A quirky roadside servo" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coastal lighthouse cliffs&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.amazonaws.com%2Fuploads%2Farticles%2Fetnel2w3xcvxyyprkpzs.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%2Fetnel2w3xcvxyyprkpzs.png" alt="Coastal lighthouse cliffs" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mangroves&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.amazonaws.com%2Fuploads%2Farticles%2Fg0ztu58sdhu4qhjp85jo.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%2Fg0ztu58sdhu4qhjp85jo.png" alt="Mangroves" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blue Mountains&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.amazonaws.com%2Fuploads%2Farticles%2Fmlc2jsga9j9bg74gz8md.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%2Fmlc2jsga9j9bg74gz8md.png" alt="Blue Mountains" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nullarbor&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.amazonaws.com%2Fuploads%2Farticles%2Fuw812u5j9mrvkods4374.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%2Fuw812u5j9mrvkods4374.png" alt="Nullarbor" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observatory&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.amazonaws.com%2Fuploads%2Farticles%2Fc2nbxpisx4c9180rbmn0.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%2Fc2nbxpisx4c9180rbmn0.png" alt="Observatory" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Aurora-lit Tasmania&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.amazonaws.com%2Fuploads%2Farticles%2F3np9bqwy5v1e8sgucpyr.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%2F3np9bqwy5v1e8sgucpyr.png" alt="Aurora-lit Tasmania" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each region introduces new obstacles and visual themes while keeping the action moving.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Nod to Alan Turing
&lt;/h3&gt;

&lt;p&gt;Since June is also Alan Turing's birth month, I wanted to include a tribute without turning the game into a history lesson.&lt;/p&gt;

&lt;p&gt;The game's antagonist, the &lt;strong&gt;Turing Engine&lt;/strong&gt;, believes that endless night is the most logical state of existence.&lt;/p&gt;

&lt;p&gt;Players encounter quick puzzles based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pattern recognition&lt;/li&gt;
&lt;li&gt;Binary switches&lt;/li&gt;
&lt;li&gt;Signal routing&lt;/li&gt;
&lt;li&gt;Light circuits&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.amazonaws.com%2Fuploads%2Farticles%2F4y3cf5wd54puvigy02px.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%2F4y3cf5wd54puvigy02px.png" alt="Puzzle" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The puzzles are short and designed to support the action rather than interrupt it.&lt;/p&gt;

&lt;h3&gt;
  
  
  TM Corridors — &lt;em&gt;Turing's Run&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;At three points in the expedition, the world stops being a runner and becomes a &lt;strong&gt;Turing Machine you operate by hand&lt;/strong&gt;. The level fades away and you step into a glowing sci-fi &lt;strong&gt;corridor&lt;/strong&gt; rendered in one-point perspective — a hallway that &lt;em&gt;is&lt;/em&gt; the tape, charging toward a vanishing-point portal.&lt;/p&gt;

&lt;p&gt;The story is told in three chapters, framed as &lt;strong&gt;Alan Turing bypassing three sealed gates&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%2F46dfjh99xmhttnxup334.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%2F46dfjh99xmhttnxup334.png" alt="Bit Flipper" width="799" height="403"&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%2Fr0rw901sddt7q34blp28.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%2Fr0rw901sddt7q34blp28.png" alt="Parity Checker" width="799" height="403"&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%2Fgi6uy40nzutu04mklodb.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%2Fgi6uy40nzutu04mklodb.png" alt="Even-Length Protocol" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Corridor&lt;/th&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;The gate demands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;I of III&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Bit Flipper&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Invert every bit until the blank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;II of III&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Parity Checker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open only for an even number of &lt;code&gt;1&lt;/code&gt;s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;III of III&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Even-Length Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accept only an even-length message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Art Direction
&lt;/h3&gt;

&lt;p&gt;One of the biggest pivots during development was moving away from a bright arcade aesthetic and leaning into a treasure-hunting adventure style.&lt;/p&gt;

&lt;p&gt;The world uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Warm desert colours&lt;/li&gt;
&lt;li&gt;Ancient ruins&lt;/li&gt;
&lt;li&gt;Dust particles&lt;/li&gt;
&lt;li&gt;Golden sunlight effects&lt;/li&gt;
&lt;li&gt;Treasure-map inspired UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This direction helped tie together the themes of the solstice, exploration, and treasure hunt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technology
&lt;/h3&gt;

&lt;p&gt;The game was built using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vanilla JavaScript — game.js is the entire game engine (~6500+ lines)&lt;/li&gt;
&lt;li&gt;Three.js — 3D terrain rendering behind the 2D gameplay HUD&lt;/li&gt;
&lt;li&gt;Web Audio API&lt;/li&gt;
&lt;li&gt;HTML/CSS — index.html, inline UI modals, HUD&lt;/li&gt;
&lt;li&gt;Vercel Deployment&lt;/li&gt;
&lt;li&gt;GitHub Copilot for development acceleration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The focus throughout was on keeping the project lightweight, responsive, and playable directly in the browser.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Best Ode to Alan Turing
&lt;/h3&gt;

&lt;p&gt;Dawn Dashers is a love letter to Alan Turing, the mathematician, codebreaker, and father of Theoretical computer scientist who was born on 23 June 1912.&lt;/p&gt;

&lt;p&gt;Rather than focusing on historical events, I wanted to celebrate the ideas that made his work so influential: logic, patterns, problem-solving, and computation.&lt;/p&gt;

&lt;p&gt;The Turing Engine serves as the main antagonist, and players overcome its challenges through quick puzzles inspired by computational thinking.&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%2Ffvxsvf7aop53f5rhsjg7.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%2Ffvxsvf7aop53f5rhsjg7.png" alt="Treasure game" width="799" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;P.S. For fellow nerds (like me 😄), each puzzle comes with a little extra bit of knowledge. Just click "Learn why this puzzle matters".&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  In the characters
&lt;/h4&gt;

&lt;p&gt;The 27 playable dashers are all Australian animals  and each character's &lt;strong&gt;power&lt;/strong&gt; and &lt;strong&gt;quirk&lt;/strong&gt; are inspired by&lt;br&gt;
concepts from Turing's work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Emu&lt;/em&gt; — relentless forward motion (Turing's unstoppable curiosity)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Wombat&lt;/em&gt; — burrow-dodge (evasion, steganography)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Echidna&lt;/em&gt; — spiny defence (cryptographic hardening)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Cassowary&lt;/em&gt; — apex predator speed (Colossus, the world's first electronic computer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "special" dashers can only be unlocked by solving enough treasure puzzles — a direct nod to&lt;br&gt;
the idea that Turing's deeper insights were hidden behind layers of prior work.&lt;/p&gt;
&lt;h4&gt;
  
  
  In the Game engine
&lt;/h4&gt;

&lt;p&gt;The game's lifecycle is modelled as a &lt;strong&gt;real Turing Machine&lt;/strong&gt; (see &lt;em&gt;Architecture&lt;/em&gt; below).&lt;br&gt;
States are labelled &lt;code&gt;q_*&lt;/code&gt;, input symbols &lt;code&gt;σ_*&lt;/code&gt;, and every transition is written to a &lt;strong&gt;tape&lt;/strong&gt;&lt;br&gt;
with a head that advances &lt;code&gt;R&lt;/code&gt;, stays &lt;code&gt;S&lt;/code&gt;, or — on a level abort — rewinds &lt;code&gt;L&lt;/code&gt;.&lt;br&gt;
This isn't just a metaphor: the implementation is a literal δ(q, σ) → (q′, dir) transition table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────┐
│  Browser                                                            │
│                                                                     │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │  game.js  (core)                                             │   │
│  │                                                              │   │
│  │   Run loop / input                                           │   │
│  │        │                                                     │   │
│  │        │  sendFlow(σ_*)        ┌─────────────────────────┐   │   │
│  │        ├─────────────────────► │   game-state.js         │   │   │
│  │        │  ◄── state flags ──── │                         │   │   │
│  │        │                       │   Turing Machine        │   │   │
│  │        │                       │   Q  states  (q_*)      │   │   │
│  │        │                       │   Σ  symbols (σ_*)      │   │   │
│  │        │                       │   δ  transition table   │   │   │
│  │        │                       │   tape[]  head ptr      │   │   │
│  │        │                       └─────────────────────────┘   │   │
│  │        │                                                      │   │
│  │        ├── Puzzle pick &amp;amp; dedup ──► puzzle-tracker.js          │   │
│  │        │                               │                      │   │
│  │        │                          puzzle-filter.js            │   │
│  │        │                               │                      │   │
│  │        │                          puzzle-data.js              │   │
│  │        │                          ├── questions-easy.js       │   │
│  │        │                          ├── questions-medium.js     │   │
│  │        │                          └── questions-hard.js       │   │
│  │        │                                                      │   │
│  │        ├── Three.js + Canvas render                           │   │
│  │        │        └── terrain-styles.js                         │   │
│  │        │                                                      │   │
│  │        └── Config reads                                       │   │
│  │             ├── character-config.js                           │   │
│  │             ├── game-data.js                                   │   │
│  │             └── game-settings.js                              │   │
│  │                                                               │   │
│  │  game-ui.js ──ctx callbacks──► game.js                        │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                     │
│  index.html  (loads all modules via &amp;lt;script&amp;gt; tags)                  │
└─────────────────────────────────────────────────────────────────────┘
         ▲
         │  served by
┌────────┴────────┐
│  Nginx (Docker) │
│  docker-compose │
└─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best Google AI Usage
&lt;/h3&gt;

&lt;p&gt;I'm also submitting for &lt;strong&gt;Best Google AI Usage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'll be honest: I'm much more comfortable building systems and writing backend code than designing beautiful interfaces.&lt;/p&gt;

&lt;p&gt;One of the challenges during this project was figuring out how to make &lt;em&gt;Dawn Dashers&lt;/em&gt; feel cohesive visually. I had a rough idea of the atmosphere I wanted—an Australian treasure-hunting adventure, but translating that into colours, layouts, typography, and UI design isn't my strongest skill.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Google Stitch&lt;/strong&gt; helped.&lt;/p&gt;

&lt;p&gt;I used Stitch to explore rapidly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI layouts and screen structure&lt;/li&gt;
&lt;li&gt;Menu and HUD designs&lt;/li&gt;
&lt;li&gt;Colour palettes&lt;/li&gt;
&lt;li&gt;Typography combinations&lt;/li&gt;
&lt;li&gt;Visual hierarchy&lt;/li&gt;
&lt;li&gt;Overall art direction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What impressed me most was how easy it was to iterate. Instead of spending hours moving elements around in a design tool, I could describe the feeling I wanted and quickly generate multiple directions to evaluate.&lt;/p&gt;

&lt;p&gt;For example, I started with a fairly generic arcade-style look, but through a few iterations in Stitch, I landed on a much stronger visual identity built around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Warm desert colours&lt;/li&gt;
&lt;li&gt;Treasure-map inspired UI&lt;/li&gt;
&lt;li&gt;Gold accents&lt;/li&gt;
&lt;li&gt;Weathered parchment styling&lt;/li&gt;
&lt;li&gt;Adventure-game aesthetics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those explorations directly influenced the final design language of the game.&lt;/p&gt;

&lt;p&gt;I also used Gemini extensively during the design phase.&lt;/p&gt;

&lt;p&gt;One challenge was incorporating Alan Turing-inspired puzzles without making the game feel like a classroom exercise. I wanted puzzles based on logic, patterns, and computation, but they also needed to feel like they belonged in an Australian treasure-hunting adventure.&lt;/p&gt;

&lt;p&gt;Gemini helped me brainstorm and refine puzzle concepts such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pattern recognition challenges&lt;/li&gt;
&lt;li&gt;Binary switch puzzles&lt;/li&gt;
&lt;li&gt;Signal routing mechanics&lt;/li&gt;
&lt;li&gt;Treasure clues tied to Australian locations and wildlife&lt;/li&gt;
&lt;li&gt;Turing-inspired logic challenges that could be solved quickly during gameplay&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, I used Gemini to take abstract puzzle ideas and re-theme them into Australian contexts, turning generic logic puzzles into clues involving lighthouses, Outback landmarks, wildlife, and hidden Sun Fragments.&lt;/p&gt;

&lt;p&gt;The combination of Stitch and Gemini helped bridge two areas where I typically spend a lot of time: design exploration and content creation. Instead of starting from a blank page, I could rapidly iterate on ideas, evaluate options, and focus more of my time on building and polishing the game itself.&lt;/p&gt;

&lt;p&gt;The AI tools didn't replace the creative decisions—they helped me reach better ones faster.&lt;/p&gt;

&lt;p&gt;As a solo developer, having a tool that could help bridge the gap between "I know what I want it to feel like" and "I know how to design it" was incredibly valuable. It let me spend more time building gameplay while still ending up with a much more polished visual experience.&lt;/p&gt;

&lt;p&gt;Google Stitch didn't replace the design decisions—it helped me discover and refine them much faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Happy Dashing !!!
&lt;/h3&gt;

&lt;p&gt;Thanks for checking out Dawn Dashers 🌅&lt;/p&gt;

&lt;p&gt;It was a lot of fun combining the June Solstice, Australian landscapes, treasure hunting, arcade runners, and a small nod to Alan Turing into one adventure.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Is Zero Trust Enough for Agentic Systems?</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Tue, 02 Jun 2026 13:45:15 +0000</pubDate>
      <link>https://dev.to/ujja/is-zero-trust-enough-for-agentic-systems-ge9</link>
      <guid>https://dev.to/ujja/is-zero-trust-enough-for-agentic-systems-ge9</guid>
      <description>&lt;p&gt;I’ve always had a soft spot for authentication systems.&lt;/p&gt;

&lt;p&gt;About seven years ago, I started working on auth, and something just &lt;em&gt;clicked&lt;/em&gt;. What began as login flows slowly turned into a deeper curiosity about identity, permissions, and how systems decide who gets to do what.&lt;/p&gt;

&lt;p&gt;Over time, I’ve worked with tools like Keycloak, Auth0, Okta, and Ping Identity. Different platforms, same core idea kept showing up:&lt;/p&gt;

&lt;p&gt;Never trust. Always verify.&lt;/p&gt;

&lt;p&gt;For a long time, that felt like the finish line.&lt;/p&gt;

&lt;p&gt;Lately, though, it’s starting to feel more like the starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The moment systems stop responding… and start acting&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;During a recent hackathon, I built something called PlanetLedger. The idea was simple: upload your bank statement to see your environmental impact.&lt;/p&gt;

&lt;p&gt;But under the hood, it didn’t behave like a typical app.&lt;/p&gt;

&lt;p&gt;An upload didn’t just return a response. It kicked off a series of actions. First, it parsed transactions. Then, it categorised vendors. Next, it calculated a score. It also generated insights and triggered notifications. Finally, it updated a memory timeline for future runs.&lt;/p&gt;

&lt;p&gt;All of that started from a single call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;openClawChainedTrigger&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;previousScore&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Which quietly unfolded into:&lt;/p&gt;

&lt;p&gt;transactions_uploaded → score_calculated → insights_generated → score_improved&lt;/p&gt;

&lt;p&gt;At some point while building this, I realised something had changed.&lt;/p&gt;

&lt;p&gt;This wasn’t a request-response system anymore.&lt;/p&gt;

&lt;p&gt;It was a system that &lt;em&gt;kept acting&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where Zero Trust fits perfectly… and where it doesn’t&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;From a security perspective, I did everything by the book.&lt;/p&gt;

&lt;p&gt;Every API route checks whether the agent is allowed to perform an action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canPerform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FGA_RULES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scopes are tightly defined. Every action is verified. Nothing runs without permission.&lt;/p&gt;

&lt;p&gt;If you look at this through a Zero Trust lens, it’s solid.&lt;/p&gt;

&lt;p&gt;And yet, the most interesting problems I ran into had nothing to do with access.&lt;/p&gt;

&lt;p&gt;They showed up &lt;em&gt;after&lt;/em&gt; access was granted.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The real question isn’t “can it act?” — it’s “should it keep acting?”&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Take something as simple as a high-impact alert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;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;highImpactAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pseudonymize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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;score&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impactScore&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nf"&gt;pushNotification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&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;high_impact&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High-Impact Alert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highImpactCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;impact&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="nx"&gt;detected&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;Everything here is valid.&lt;/p&gt;

&lt;p&gt;The user is authenticated. The system has permission. The action is allowed.&lt;/p&gt;

&lt;p&gt;But there’s a more subtle question hiding underneath:&lt;/p&gt;

&lt;p&gt;Should this alert be triggered right now?&lt;/p&gt;

&lt;p&gt;Because that depends on things Zero Trust doesn’t see.&lt;/p&gt;

&lt;p&gt;Maybe the score calculation was slightly off. The categorisation step earlier in the pipeline may have misfired. Maybe the context wasn’t complete yet.&lt;/p&gt;

&lt;p&gt;Each step is correct individually.&lt;/p&gt;

&lt;p&gt;But the outcome might still be… wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When valid steps create questionable behaviour&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;One of the most interesting things about the OpenClaw pipeline was how easy it was to compose behaviour.&lt;/p&gt;

&lt;p&gt;You can attach multiple workflows to the same event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;registerOpenClawTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transactions_uploaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;autoInsightOnUpload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nf"&gt;registerOpenClawTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transactions_uploaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;highImpactAlert&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On their own, these are harmless.&lt;/p&gt;

&lt;p&gt;One generates insights. The other sends alerts.&lt;/p&gt;

&lt;p&gt;But together, they begin to shape how a user interprets their financial behaviour. Add weekly reports, detect patterns, and make recommendations. Then, the system starts to influence decisions.&lt;/p&gt;

&lt;p&gt;And that’s where things get tricky.&lt;/p&gt;

&lt;p&gt;Because even if every individual step is valid, the &lt;em&gt;overall direction&lt;/em&gt; can drift.&lt;/p&gt;

&lt;p&gt;Zero Trust doesn’t track that. It validates moments, not trajectories.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Intent is where things start to slip&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;PlanetLedger uses a RAG layer to generate insights grounded in user data:&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;ragContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildRagContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&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;insights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildAgentInsights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ragContext&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works surprisingly well most of the time.&lt;/p&gt;

&lt;p&gt;But occasionally, you’ll see something slightly off. A recommendation that technically makes sense but doesn’t quite match what you’d expect. A pattern that’s overemphasised. A suggestion that feels a bit… disconnected.&lt;/p&gt;

&lt;p&gt;Nothing is broken.&lt;/p&gt;

&lt;p&gt;But something feels misaligned.&lt;/p&gt;

&lt;p&gt;That’s the gap.&lt;/p&gt;

&lt;p&gt;Zero Trust ensures the system is allowed to act. It doesn’t ensure the system is acting &lt;em&gt;with the right intent&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The part that surprised me: drift&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The more I worked with chained workflows, the more I noticed this subtle effect.&lt;/p&gt;

&lt;p&gt;If something is slightly off early in the pipeline — say, a categorisation edge case — that error doesn’t stay isolated. It propagates.&lt;/p&gt;

&lt;p&gt;It affects scoring. Which affects insights. Which affects alerts. Which affects what the user sees.&lt;/p&gt;

&lt;p&gt;By the time it surfaces, it’s no longer obvious where it started.&lt;/p&gt;

&lt;p&gt;Everything along the way was technically valid.&lt;/p&gt;

&lt;p&gt;But the outcome feels wrong.&lt;/p&gt;

&lt;p&gt;That’s drift.&lt;/p&gt;

&lt;p&gt;And it’s not something traditional access control is designed to catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;So what actually helps?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I didn’t sit down with a framework for this. Most of these ideas came from trying to make PlanetLedger behave more predictably.&lt;/p&gt;

&lt;p&gt;One thing that helped was thinking beyond request-level authorisation.&lt;/p&gt;

&lt;p&gt;Because the pipeline continues after the initial API response, decisions are being made in a flow, not a single moment. That means authorisation needs to become aware of state, timing, and sequence — not just whether a token is valid.&lt;/p&gt;

&lt;p&gt;Another thing that made a difference was leaning into deterministic rules where it mattered. The scoring system, for example, is intentionally simple and explainable. Not because an LLM couldn’t do it, but because predictability is a form of control.&lt;/p&gt;

&lt;p&gt;The structure of OpenClaw itself also acts as a constraint. It’s deliberately minimal — no retries, no replay, no distributed guarantees. At first, that feels like a limitation, but it actually forces the system to operate within clear, bounded behaviour. It can only do what the registered workflows define.&lt;/p&gt;

&lt;p&gt;Logging was another area that evolved quickly. Moving from plain logs to structured outputs made it easier to trace what happened. But even that surfaced a deeper need: understanding &lt;em&gt;why&lt;/em&gt; a decision was made, not just what happened.&lt;/p&gt;

&lt;p&gt;And then there’s step-up.&lt;/p&gt;

&lt;p&gt;In traditional systems, step-up authentication is about verifying identity at critical moments. But in systems like this, identity isn’t usually the weak point. The question isn’t “is this really the user?”&lt;/p&gt;

&lt;p&gt;It’s “Should this decision go through?”&lt;/p&gt;

&lt;p&gt;That shift—from checking identities to validating decisions—looks small. But it changes how you make safeguards.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What this starts to look like in practice&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After building something like PlanetLedger, the architecture stops being just about access control.&lt;/p&gt;

&lt;p&gt;Zero Trust still sits at the base, making sure only the right actors can do the right things.&lt;/p&gt;

&lt;p&gt;But on top of that, you start layering systems that understand behaviour over time. Systems guide actions. They spot patterns and notice when things feel off. Sometimes, they bring a human back in when the stakes are high.&lt;/p&gt;

&lt;p&gt;None of these replaces Zero Trust.&lt;/p&gt;

&lt;p&gt;They fill in the gaps that it was never designed to cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final thought&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Zero Trust is still essential.&lt;/p&gt;

&lt;p&gt;Without it, systems like PlanetLedger wouldn’t hold up for a second.&lt;/p&gt;

&lt;p&gt;But once systems move from simply responding to requests to continuously making decisions…&lt;/p&gt;

&lt;p&gt;Trust stops being something you verify once.&lt;/p&gt;

&lt;p&gt;It becomes something you evaluate, quietly and continuously, across everything the system does.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>security</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Building an Offline-First Bushfire Response Platform With Hermes Agent</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Thu, 28 May 2026 01:51:59 +0000</pubDate>
      <link>https://dev.to/ujja/building-an-offline-first-bushfire-response-platform-with-hermes-agent-4m0a</link>
      <guid>https://dev.to/ujja/building-an-offline-first-bushfire-response-platform-with-hermes-agent-4m0a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hermes-agent-2026-05-15"&gt;Hermes Agent Challenge&lt;/a&gt;: Build With Hermes Agent&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Project Haven&lt;/strong&gt; is an AI-powered emergency response platform for bushfire preparedness — evacuation routing, tiered real-time alerts, government recovery grant discovery, and offline-first PWA support for when mobile networks go down mid-crisis.&lt;/p&gt;

&lt;p&gt;The platform was originally a 46-hour hackathon build (GovHack 2024 — we won). This year I &lt;a href="https://dev.to/ujja/from-govhack-win-to-something-that-actually-matters-2mmi"&gt;brought it back from the dead and rebuilt &lt;/a&gt; it properly: event-driven microservices, contract-first OpenAPI specs, a prediction engine based on XGBoost weights from our historical bushfire notebooks, and a fully offline-capable React PWA.&lt;/p&gt;

&lt;p&gt;The one piece that was always a hollow mock was the &lt;strong&gt;AI Assistant&lt;/strong&gt; — the in-app emergency guidance chat. It had a &lt;code&gt;setTimeout&lt;/code&gt; pretending to think and a big &lt;code&gt;switch&lt;/code&gt; statement of canned responses. Every time I looked at it I felt embarrassed.&lt;/p&gt;

&lt;p&gt;Hermes fixed that.&lt;/p&gt;

&lt;p&gt;Hermes Agent is now the live brain behind three things in Project Haven:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;In-app emergency guidance&lt;/strong&gt; — the AI Assistant page calls Hermes via its OpenAI-compatible &lt;code&gt;/v1/chat/completions&lt;/code&gt; API, grounded with a system prompt that constrains it to verified Australian emergency protocols and instructs it to escalate emergency situations to 000.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduled fire-risk briefings&lt;/strong&gt; — Hermes runs a natural-language cronjob that fires every morning at 6am during fire season, calls the Bureau of Meteorology and NSW RFS feeds, synthesises a risk summary, and publishes it as an event into the alert pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recovery grant research&lt;/strong&gt; — when a user marks themselves as "in recovery", Hermes autonomously searches for current government grant programs (NDRA, state schemes, Services Australia), compares them against the user's declared situation, and adds matched recommendations to the recommendation service DB.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/ujjavala/project-haven#" rel="noopener noreferrer"&gt;project-haven&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Running it locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One command spins up 6 microservices, an API gateway, PostgreSQL instances, RabbitMQ, and the React PWA at &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To trigger the full prediction → alert pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/weather &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"lat":-33.87,"lng":151.21,"temperature":42,"windSpeed":80,"humidity":10,"season":"summer","vegetationDensity":0.9}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simulates an extreme weather event near Sydney, runs it through the prediction engine, and fires a CRITICAL alert through the system within seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

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

&lt;p&gt;&lt;strong&gt;AI Assistant — Hermes-powered emergency guidance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The AI Assistant page now sends messages to Hermes via the api-gateway (&lt;code&gt;/assistant/v1/chat/completions&lt;/code&gt;). Hermes has persistent memory across sessions via &lt;code&gt;X-Hermes-Session-Key&lt;/code&gt;, so if a user opened the app two days ago and said "I'm in the Blue Mountains", Hermes still knows that when they say "the fire is getting closer" today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled briefings appearing in the alert feed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every morning during fire season, Hermes fetches live fire danger ratings from the NSW RFS API, synthesises a 3-sentence risk summary grounded in real data, and injects it as a &lt;code&gt;feed.created&lt;/code&gt; event. The event propagates through RabbitMQ to the alert service and appears in-app within seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recovery grant matching&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A user marks the "Recovery" scenario. Hermes is asked to research grants available for their postcode and situation. It uses its web search tool to check current Services Australia pages — bypassing the staleness problem of any static dataset — and returns structured results that get persisted back to the recommendations table.&lt;/p&gt;




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

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/ujja/project-haven" rel="noopener noreferrer"&gt;project-haven&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Running it locally
&lt;/h3&gt;

&lt;p&gt;The full stack — Haven microservices + Hermes Agent + a local LLM — runs entirely on your machine. No external inference API needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt; Docker Desktop 27+, 8 GB RAM minimum (16 GB recommended), macOS / Linux / WSL2 on Windows.&lt;/p&gt;

&lt;p&gt;Ollama, the model, all backend services, and the frontend are all managed by Docker Compose. No host-level installation needed beyond Docker itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1 — Clone and start everything
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ujja/project-haven.git
&lt;span class="nb"&gt;cd &lt;/span&gt;project-haven
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first start, the &lt;code&gt;ollama-init&lt;/code&gt; container pulls &lt;code&gt;nous-hermes2&lt;/code&gt; (~4.5 GB) automatically. The api-gateway waits for the pull to complete before starting. Subsequent starts are fast — the model is cached in a Docker volume.&lt;/p&gt;

&lt;p&gt;Progress is streamed to the compose log. Once you see &lt;code&gt;api-gateway | api-gateway listening on port 8080&lt;/code&gt;, everything is ready.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2 — Test the full pipeline
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simulate an extreme weather event near Sydney → triggers prediction → alert&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/weather &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"lat":-33.87,"lng":151.21,"temperature":42,"windSpeed":80,"humidity":10,"season":"summer","vegetationDensity":0.9}'&lt;/span&gt;

&lt;span class="c"&gt;# Ask the AI assistant directly&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/assistant/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"nous-hermes2","messages":[{"role":"user","content":"There is a bushfire near me. What do I do right now?"}]}'&lt;/span&gt;

&lt;span class="c"&gt;# Search live recovery grants&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/recommendations/research &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"postcode":"2750","situation":"home destroyed by bushfire"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Architecture of the local stack
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React PWA (port 3000)
        ↓
API Gateway (port 8080)
   ├── /assistant/*  →  Ollama /v1/chat/completions (nous-hermes2)
   ├── /recommendations/research  →  Ollama (structured JSON)
   └── node-cron @ 6am  →  Ollama → feed-service
        ↓
Ollama container (port 11434, haven-net)
        ↓
nous-hermes2 (cached in ollama-data volume)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything runs inside Docker Compose. &lt;code&gt;ollama-init&lt;/code&gt; pulls the model once on first start; the &lt;code&gt;ollama-data&lt;/code&gt; volume persists it across restarts.&lt;/p&gt;

&lt;h4&gt;
  
  
  Starter model recommendations
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nous-hermes2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~4.5 GB&lt;/td&gt;
&lt;td&gt;Default — strong instruction following, good JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma2:9b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~5.4 GB&lt;/td&gt;
&lt;td&gt;Superior JSON adherence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemma2:2b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1.6 GB&lt;/td&gt;
&lt;td&gt;Low-RAM machines, faster responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;llama3:latest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~4.7 GB&lt;/td&gt;
&lt;td&gt;General-purpose alternative&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Set &lt;code&gt;HERMES_MODEL&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; to swap. Also update the &lt;code&gt;ollama-init&lt;/code&gt; entrypoint in &lt;code&gt;docker-compose.yml&lt;/code&gt; to pull the new model.&lt;/p&gt;

&lt;p&gt;Avoid 70B-class models unless you have GPU hardware with 40+ GB VRAM.&lt;/p&gt;

&lt;h4&gt;
  
  
  Common issues
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;First start is slow:&lt;/strong&gt; The &lt;code&gt;ollama-init&lt;/code&gt; container has to download the &lt;code&gt;nous-hermes2&lt;/code&gt; model (~4.5 GB). Subsequent starts skip this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model too slow on CPU:&lt;/strong&gt; Edit &lt;code&gt;.env&lt;/code&gt; and set &lt;code&gt;HERMES_MODEL=gemma2:2b&lt;/code&gt; (1.6 GB, much faster). Also update the &lt;code&gt;ollama-init&lt;/code&gt; entrypoint in &lt;code&gt;docker-compose.yml&lt;/code&gt; to pull the new model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WSL2 networking problems:&lt;/strong&gt; Volume mounts and bridge networking have edge cases — increasing Docker's memory allocation in Docker Desktop settings usually resolves them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux GPU acceleration:&lt;/strong&gt; Uncomment the &lt;code&gt;deploy&lt;/code&gt; block in the &lt;code&gt;ollama&lt;/code&gt; service in &lt;code&gt;docker-compose.yml&lt;/code&gt; and ensure &lt;code&gt;nvidia-container-toolkit&lt;/code&gt; is installed.&lt;/p&gt;




&lt;h3&gt;
  
  
  My Tech Stack
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI backbone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nous Hermes 2 via Ollama (OpenAI-compatible, locally hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 18, TypeScript, Vite, Workbox PWA, Leaflet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js 20, Express, TypeScript — 6 microservices + API gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Messaging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RabbitMQ (event-driven: weather → prediction → alert pipeline)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Databases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL (per-service)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;XGBoost (Python notebooks → TypeScript heuristic engine)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Digital Atlas / Geoscience Australia ArcGIS REST APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Infra&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Docker Compose, multi-stage builds, shared &lt;code&gt;@haven/shared&lt;/code&gt; npm package&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How I Used Hermes Agent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. OpenAI-Compatible Inference — Zero New API Surface
&lt;/h3&gt;

&lt;p&gt;Ollama exposes &lt;code&gt;POST /v1/chat/completions&lt;/code&gt; exactly like the OpenAI SDK expects. It runs as a Docker Compose service (&lt;code&gt;ollama&lt;/code&gt;) on the internal &lt;code&gt;haven-net&lt;/code&gt; network. The api-gateway proxies &lt;code&gt;/assistant/*&lt;/code&gt; directly to &lt;code&gt;http://ollama:11434&lt;/code&gt;. No separate agent container, no extra port, no API key management between services.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;AIAssistant.tsx&lt;/code&gt;, the &lt;code&gt;getAIResponseHermes()&lt;/code&gt; function — previously a &lt;code&gt;setTimeout&lt;/code&gt; + &lt;code&gt;switch&lt;/code&gt; statement — became a real 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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAIResponseHermes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/assistant/v1/chat/completions&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nous-hermes2&lt;/span&gt;&lt;span class="dl"&gt;'&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="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="s1"&gt;system&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;HAVEN_SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// emergency protocols, escalate to 000, AU context&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="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Ollama responded with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;res&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The api-gateway proxy strips any client-side auth before forwarding to Ollama. A stable session key is stored in &lt;code&gt;localStorage&lt;/code&gt; per browser for memory scoping, ready for if a stateful layer is added upstream.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Scheduled Fire Risk Briefings — Natural Language Cron
&lt;/h3&gt;

&lt;p&gt;Hermes's cron scheduling is genuinely one of its most underrated features. Instead of writing a Node worker with &lt;code&gt;node-cron&lt;/code&gt;, a BOM API client, a response parser, and an event publisher, I wrote this in the Hermes config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fire-risk-briefing&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Oct-Mar)"&lt;/span&gt;   &lt;span class="c1"&gt;# 6am daily, fire season only&lt;/span&gt;
    &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Check the current fire danger ratings for NSW, VIC, SA, and WA from the&lt;/span&gt;
      &lt;span class="s"&gt;Bureau of Meteorology and RFS feeds. Synthesise a 3-sentence morning&lt;/span&gt;
      &lt;span class="s"&gt;briefing — severity level, highest-risk regions, and one action&lt;/span&gt;
      &lt;span class="s"&gt;recommendation. Keep it under 80 words. Respond with JSON:&lt;/span&gt;
      &lt;span class="s"&gt;{ "severity": "LOW|MEDIUM|HIGH|EXTREME|CATASTROPHIC", "summary": "..." }&lt;/span&gt;
    &lt;span class="na"&gt;deliver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://api-gateway:8080/feeds&lt;/span&gt;
    &lt;span class="na"&gt;skills&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;web_search&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hermes handles the scheduling, the web fetch, the summarisation, and the HTTP delivery. The &lt;code&gt;/feeds&lt;/code&gt; endpoint in the feed service receives the JSON payload and publishes it as a &lt;code&gt;feed.created&lt;/code&gt; event. The entire pipeline — external data → summary → in-app alert — runs without any new code.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Recovery Grant Research — Delegating Agentic Workloads
&lt;/h3&gt;

&lt;p&gt;The recommendation service seeds static government programs at startup. But government grants change — new schemes open after disasters, eligibility criteria shift, application portals go down and come back up.&lt;/p&gt;

&lt;p&gt;For the recovery scenario, I added a route in the api-gateway that delegates a research task to Hermes:&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;// api-gateway: POST /recommendations/research&lt;/span&gt;
&lt;span class="c1"&gt;// HERMES_BASE = http://ollama:11434 (set via env in docker-compose)&lt;/span&gt;
&lt;span class="c1"&gt;// HERMES_MODEL = nous-hermes2 (default, overridable via HERMES_MODEL env var)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/recommendations/research&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&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="k"&gt;async &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;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;situation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hermesRes&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HERMES_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/chat/completions`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HERMES_MODEL&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="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="s1"&gt;system&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are an Australian emergency recovery specialist. Return ONLY a valid JSON array — no markdown fences, no commentary, just the array.&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Research current Australian government disaster recovery grants
            available for postcode &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;postcode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Situation: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;situation&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.
            Return JSON array of { title, provider, description, applicationUrl, eligibilitySummary }.
            Only include currently open programs with verified URLs.`&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;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="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;data&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;hermesRes&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Strip accidental markdown fences if the model wraps the JSON&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&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="err"&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;endraw&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;/, ''&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;.replace&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```$/, '').trim();
  const grants = JSON.parse(cleaned);
  res.json({ grants });
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hermes uses its web search tool to check current Services Australia, state government, and Red Cross pages. It returns structured JSON. The gateway upserts the results into the recommendation service — live, verified, current — not frozen in a seed file.&lt;/p&gt;

&lt;p&gt;The key agentic capability here is &lt;strong&gt;web search grounded in the real prompt&lt;/strong&gt;: Hermes significantly reduced stale or hallucinated grant recommendations by grounding responses in live web results rather than parametric knowledge. It fetches the actual pages, checks them, and only returns what it finds. For emergency advice, that correctness bar is non-negotiable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Hermes vs. the Alternatives — An Honest Comparison
&lt;/h3&gt;

&lt;p&gt;Before landing on Hermes, I experimented with a few other locally-run models. Here's what that comparison actually looked like for an emergency-context application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Llama 3.1 / 3.2 (via Ollama)
&lt;/h4&gt;

&lt;p&gt;Llama is the obvious first stop for local inference. I ran Llama 3.1 8B and 3.2 3B through Ollama and pointed the same system prompt at them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt; Ollama's OpenAI-compatible &lt;code&gt;/api/chat&lt;/code&gt; endpoint made drop-in testing easy. Llama 3.2 3B is genuinely fast on Apple Silicon — response latency was better than Hermes on equivalent hardware. For simple question-answering against a fixed system prompt, the outputs were reasonable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt; Neither model had built-in tool execution, persistent session memory, or a scheduler — all three capabilities I needed. To replicate Hermes's cron briefings with Llama, I would have written a Node cron worker, a separate BOM API client, a response parser, an event publisher, and a memory store. That's four services Hermes replaced with a YAML block. Llama also had a notable tendency toward elaboration — answers were often 3-4× too long for a crisis UX where brevity is safety-critical. Prompt engineering helped, but it was a constant battle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Great for straight inference tasks where you're bringing your own orchestration layer. Not the right fit when you need the agent capabilities without the plumbing.&lt;/p&gt;




&lt;h4&gt;
  
  
  Gemma 2 (9B, via Ollama)
&lt;/h4&gt;

&lt;p&gt;Google's Gemma 2 9B was the most pleasant surprise in terms of raw instruction-following. It respected format constraints (JSON output, word limits) more reliably than anything else I tested at this size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked:&lt;/strong&gt; JSON output adherence was excellent. When I told it to return &lt;code&gt;{ "severity": "...", "summary": "..." }&lt;/code&gt;, it almost always did — no markdown wrapping, no prose preamble. That's unusually good at 9B parameters. It also handled the Australian emergency context well without needing extensive ground-truth examples in the system prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What didn't:&lt;/strong&gt; Same infrastructure gap as Llama — no native tool use, no session memory, no scheduling. Gemma 2's knowledge cutoff also made it confidently wrong about post-2024 government programs (it cited DRFA schemes that had been superseded). For a platform that needs to tell people where to apply for grants right now, that's a hard failure mode. Web search grounding isn't optional here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Best raw model for structured output tasks at the 7-9B tier. If I ever strip Hermes out and build the orchestration layer myself, Gemma 2 is what I'd put under it.&lt;/p&gt;




&lt;h4&gt;
  
  
  Mistral 7B / Mistral-Nemo
&lt;/h4&gt;

&lt;p&gt;I tried Mistral 7B Instruct and Mistral-Nemo (12B) briefly. Both are fast and capable general-purpose models. But in the emergency context, Mistral had one consistent problem: it over-hedged.&lt;/p&gt;

&lt;p&gt;Every answer about what to do in a bushfire came back wrapped in "I am not a qualified emergency services professional and this should not be taken as official advice…" disclaimers that would fill half the screen on a mobile device. I understand why models are trained to do this. But during an actual emergency, a response that leads with three sentences of disclaimer before telling someone to leave is arguably worse than no AI at all. Getting Mistral to drop the hedging without also dropping the actual safety guardrails required more system prompt engineering than the ROI justified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; Capable model, wrong default behaviour for a high-stakes UX. Taming it is possible but expensive in prompt tokens and iteration time.&lt;/p&gt;




&lt;h4&gt;
  
  
  Hermes — What It Does Better
&lt;/h4&gt;

&lt;p&gt;Against this field, here's where Hermes genuinely pulls ahead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native tool execution with no orchestration layer.&lt;/strong&gt; Web search, HTTP calls, file reads — built in. No LangChain, no custom function-calling wrapper, no managing tool schemas manually. For the grant research workflow (fetch → parse → structure → return), this is a week of orchestration code that just isn't written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent session memory across requests.&lt;/strong&gt; Every other model I tested was stateless per-request. Hermes's session-scoped memory model is simple and works reliably in practice. For a crisis scenario that plays out over days, that matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron scheduling with natural language task definitions.&lt;/strong&gt; Nothing else offers this at the infrastructure level. The fire-season briefing scheduler is 12 lines of YAML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stays on the wire it's told to stay on.&lt;/strong&gt; Hermes with the Haven system prompt stays grounded in Australian emergency protocols, is instructed to escalate emergency situations to 000, and significantly reduced stale or hallucinated grant recommendations by grounding responses in live web results. The safety system prompt feels sticky in a way that required more reinforcement with Llama and Mistral.&lt;/p&gt;




&lt;h4&gt;
  
  
  Why I Chose Hermes Over More General Agent Platforms
&lt;/h4&gt;

&lt;p&gt;I also evaluated broader autonomous-agent platforms — particularly OpenClaw-style systems built around persistent personal agents, plugins, and wide capability surfaces.&lt;/p&gt;

&lt;p&gt;Those systems are impressive. But for Project Haven, they solved a different problem than the one I actually had.&lt;/p&gt;

&lt;p&gt;Project Haven is not trying to build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a personal AI operating system,&lt;/li&gt;
&lt;li&gt;a desktop automation agent,&lt;/li&gt;
&lt;li&gt;or an infinitely extensible multi-agent ecosystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It needs something much narrower and more reliable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic emergency workflows,&lt;/li&gt;
&lt;li&gt;bounded tool execution,&lt;/li&gt;
&lt;li&gt;persistent memory,&lt;/li&gt;
&lt;li&gt;scheduled autonomy,&lt;/li&gt;
&lt;li&gt;and predictable behaviour under pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction ended up mattering a lot.&lt;/p&gt;

&lt;p&gt;Platforms like OpenClaw optimise for maximum flexibility — plugins, integrations, autonomous behaviours, evolving capability graphs. Hermes feels more opinionated. In practice, that was a benefit.&lt;/p&gt;

&lt;p&gt;For an emergency-response application, I cared more about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reliability over extensibility,&lt;/li&gt;
&lt;li&gt;constrained behaviour over open-ended autonomy,&lt;/li&gt;
&lt;li&gt;and operational predictability over ecosystem breadth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tighter execution model made Hermes easier to reason about architecturally. The memory model was simpler. The scheduling primitives were built in. And the default operational surface felt significantly safer for a high-stakes context.&lt;/p&gt;

&lt;p&gt;That tradeoff means Hermes currently has a smaller ecosystem, fewer integrations, less community tooling, and less flexibility than broader agent platforms. But for Project Haven, that narrower scope was exactly the point.&lt;/p&gt;

&lt;p&gt;I didn't need an AI operating system. I needed a dependable emergency-response runtime that could live entirely inside my infrastructure stack and keep working when the situation around it stopped being normal.&lt;/p&gt;




&lt;h4&gt;
  
  
  Where Hermes Falls Short
&lt;/h4&gt;

&lt;p&gt;Being honest about the gaps matters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold start time.&lt;/strong&gt; Hermes in Docker takes noticeably longer to reach a ready state than a model served via Ollama. On my MacBook Pro M2, Ollama with Llama 3.2 3B is ready in under 5 seconds. Hermes takes 15-25 seconds to initialise its memory backend and tool registry. In a production deployment this is a non-issue (it starts once). In development, it slows iteration loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw inference speed at the same model size.&lt;/strong&gt; Hermes's overhead — memory management, tool routing, session handling — costs tokens per request. For a simple in-context QA task with no tools, Llama through Ollama will answer faster. For the agentic tasks (web search, multi-step research), that comparison flips because Hermes automates and coordinates multi-step tool execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation gaps.&lt;/strong&gt; The job scheduler YAML schema isn't well-documented — I spent more time than I'd like reading source to understand what &lt;code&gt;deliver:&lt;/code&gt; accepted and how session keys scope memory. Llama/Gemma via Ollama have significantly more community documentation. Stack Overflow has nothing for Hermes-specific issues yet; you're on the GitHub issues list and Discord.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model selection is less flexible.&lt;/strong&gt; Hermes currently offers less model flexibility than a pure Ollama workflow. If you want Gemma 2 9B's superior JSON adherence under Hermes's tool/memory layer, that combination isn't always straightforward depending on your configuration. With Ollama, you can swap models in one command. This matters if you're trying to tune cost/quality tradeoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows support is rough.&lt;/strong&gt; Docker on Windows with WSL2 works but the volume mounting and networking for Hermes's memory backend had edge cases I had to work around. On macOS and Linux it was smooth.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Hermes Was the Right Fit (Despite All That)
&lt;/h3&gt;

&lt;p&gt;A few things made Hermes specifically well-suited here over rolling a custom LLM integration:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It lives on your server.&lt;/strong&gt; Project Haven is explicitly designed for scenarios where internet connectivity is degraded. Hermes runs in the same Docker Compose stack as everything else — no dependency on an external inference API during the crisis window. The model runs locally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Persistent memory that scales with the crisis.&lt;/strong&gt; Emergency situations evolve over hours and days. A user's context from this morning matters when they're asking questions tonight. Hermes's session memory handles this automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduled autonomy fits the "prevention not reaction" model.&lt;/strong&gt; The best emergency outcome is a user who prepared before the fire arrived. Hermes's cron scheduler lets Project Haven push proactive briefings during fire season without any always-on polling infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI-compatible API means zero new client code.&lt;/strong&gt; The React frontend, the Node gateway, anything that already speaks the OpenAI format works against Hermes unmodified. No new SDK. No vendor lock-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local inference eliminates API dependency risk.&lt;/strong&gt; One thing that became obvious very quickly while testing hosted AI APIs was how fast autonomous workflows amplify usage. A single user interaction can become multiple model calls — retrieval, summarisation, reasoning, formatting, follow-up clarification. For a crisis-response platform, depending entirely on external inference APIs introduced operational and cost dependencies I wasn't comfortable with. Running Hermes locally shifted the tradeoff toward compute and infrastructure complexity instead of per-token billing and rate limits — which was the right trade for this project.&lt;/p&gt;




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

&lt;p&gt;Project Haven started as a 46-hour hackathon build, sat frozen for two years, and became the platform it was always supposed to be during this rebuild.&lt;/p&gt;

&lt;p&gt;The AI Assistant was always the most important feature and the least real. Hermes made it real — not just more capable, but genuinely appropriate for the stakes of an emergency context: server-local, memory-persistent, verifiably grounded, and invisible enough to get out of the way when someone needs an answer fast.&lt;/p&gt;

&lt;p&gt;If you're building anything where the AI output actually matters — where a wrong answer has real consequences — the pattern of running Hermes locally with a tight system prompt and opaque tool execution is one I'd recommend seriously.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with TypeScript, React, Node.js, PostgreSQL, RabbitMQ, Docker, Hermes Agent, and a lot of respect for the people who actually work bushfire emergencies.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Originally born at GovHack 2024. Finally given room to grow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hermesagentchallenge</category>
      <category>devchallenge</category>
      <category>agents</category>
    </item>
    <item>
      <title>From Govhack Win to Something That Actually Matters</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sun, 24 May 2026 12:46:29 +0000</pubDate>
      <link>https://dev.to/ujja/from-govhack-win-to-something-that-actually-matters-2mmi</link>
      <guid>https://dev.to/ujja/from-govhack-win-to-something-that-actually-matters-2mmi</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Project Haven is an AI-powered emergency response and disaster recovery platform built specifically for bushfire preparedness — helping people find safe evacuation points, receive real-time alerts, access government recovery support, and stay informed even when the internet cuts out.&lt;/p&gt;

&lt;p&gt;The core idea is simple but important: &lt;strong&gt;during a bushfire, information is survival&lt;/strong&gt;. Where are the nearest evacuation centres? How bad is the risk right now? What government grants can I apply for after my home is damaged? Project Haven tries to answer all of that — fast, reliably, and even offline.&lt;/p&gt;

&lt;p&gt;Under the hood, the platform combines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Predictive analytics&lt;/strong&gt; — a bushfire risk engine combining the McArthur FFDI Mark 5 formula (official BOM standard) with an XGBoost model trained on GA historical fire data, running as a Python sidecar blended at 80/20&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart evacuation routing&lt;/strong&gt; — nearest safe space recommendations with distance and ETA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered recovery support&lt;/strong&gt; — scenario-based government grants and services backed by live AI research via Nous Hermes 2 (real ones, like the Disaster Recovery Payment and Primary Producer Recovery Grant)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live emergency guidance&lt;/strong&gt; — an AI Assistant powered by Nous Hermes 2 running locally via Ollama; real conversational responses grounded in Australian emergency protocols, with a system prompt that escalates to 000 when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live community feed&lt;/strong&gt; — on-the-ground updates from people in affected areas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time alerts&lt;/strong&gt; — tiered notifications from low-priority feed updates to full-screen critical banners&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first PWA&lt;/strong&gt; — because during emergencies, mobile networks are the first thing to go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is fully event-driven microservices: a prediction service subscribes to weather updates, runs the FFDI engine blended with an XGBoost model-service sidecar (Python/FastAPI, trained from GA bushfire boundaries and baked into Docker at build time), publishes bushfire predictions, and the alert service automatically generates tiered notifications from those predictions. All async. All decoupled. RabbitMQ handling the message bus, PostgreSQL for persistence, React PWA for the frontend, everything containerised and wired together with Docker Compose — including a local Ollama instance serving Nous Hermes 2 for the AI Assistant, grant research, and scheduled fire-season briefings.&lt;/p&gt;

&lt;p&gt;The part I'm still most proud of? The offline-first thinking. The app caches evacuation points, alerts, and recommendations locally. If connectivity drops mid-crisis — which it will — the app keeps working.&lt;/p&gt;




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

&lt;blockquote&gt;
&lt;p&gt;🔗 &lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/ujjavala/project-haven#" rel="noopener noreferrer"&gt;project-haven&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Running it locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One command spins up 6 microservices, an API gateway, PostgreSQL instances, RabbitMQ, and the React PWA at &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To trigger the full prediction → alert pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/weather &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"lat":-33.87,"lng":151.21,"temperature":42,"windSpeed":80,"humidity":10,"season":"summer","vegetationDensity":0.9}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simulates an extreme weather event near Sydney, runs it through the prediction engine, and fires a CRITICAL alert through the system within seconds.&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%2F2y4ec9cqn3z8xrob0zxe.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%2F2y4ec9cqn3z8xrob0zxe.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2Fqv28hzwatx4q1cz9suow.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%2Fqv28hzwatx4q1cz9suow.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2F7spgr4rlmklzvn5ghemz.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%2F7spgr4rlmklzvn5ghemz.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2Fxk89u2rrhjqsjr8rct90.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%2Fxk89u2rrhjqsjr8rct90.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2Fxqfku3yjspc98c33zjhx.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%2Fxqfku3yjspc98c33zjhx.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2Fbidh0y7ko8vs2soqpsel.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%2Fbidh0y7ko8vs2soqpsel.png" alt=" " width="800" height="520"&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.amazonaws.com%2Fuploads%2Farticles%2Fn9pst2zgwfy0n6ahm6ka.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%2Fn9pst2zgwfy0n6ahm6ka.png" alt=" " width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;Okay, here's the honest version.&lt;/p&gt;

&lt;p&gt;Project Haven was built for &lt;a href="https://govhack.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;GovHack 2024&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've never done GovHack — it's a 46-hour hackathon using Australian government open data. You show up, pick a problem, build something, demo it, and hope the judges like it.&lt;/p&gt;

&lt;p&gt;I showed up with the idea of building something that could actually help people during bushfire emergencies. Real datasets. Real data analysis. A real prediction model. I used the Digital Atlas historical bushfire boundaries CSV and ABS community datasets, built XGBoost models in Jupyter notebooks, and sketched out an event-driven microservice architecture.&lt;/p&gt;

&lt;p&gt;No UI. No running backend. Just the notebooks, the numbers, and a vision for what the system &lt;em&gt;could&lt;/em&gt; be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And I won.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which was incredible. Genuinely one of those moments you don't forget.&lt;/p&gt;

&lt;p&gt;And then... the project just sat there.&lt;/p&gt;

&lt;p&gt;Because that's what happens after hackathons. The adrenaline drops. Everyone goes home. You sleep for two days. You go back to work on Monday. Life continues.&lt;/p&gt;

&lt;p&gt;The codebase stayed frozen. The notebooks stayed frozen. The architecture stayed frozen — as a snapshot of an idea that had way more potential than a 46-hour demo ever got to show.&lt;/p&gt;

&lt;p&gt;Not because the problem wasn't worth solving. Bushfire preparedness is genuinely important in Australia. Climate change is making it worse every year. The gap between what exists and what communities actually need during emergencies is still very real.&lt;/p&gt;

&lt;p&gt;But once the goal is achieved, you move on. That's just human nature.&lt;/p&gt;




&lt;p&gt;So when the &lt;strong&gt;GitHub Finish-Up-A-Thon&lt;/strong&gt; came up, I immediately thought of Project Haven.&lt;/p&gt;

&lt;p&gt;Because going back to old projects is a weird and strangely satisfying experience.&lt;/p&gt;

&lt;p&gt;You look at the code and you see everything at once — the shortcuts you took at 2am, the TODOs you never came back to, the half-implemented features, the places where "good enough for demo day" was the bar. But you also see the original intent underneath all of it. And sometimes that intent is still worth pursuing.&lt;/p&gt;

&lt;p&gt;This time, instead of treating it like a hackathon submission, I started treating it like an actual platform.&lt;/p&gt;

&lt;p&gt;That meant a completely different mindset:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (hackathon mode):&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A system design architecture diagram&lt;/li&gt;
&lt;li&gt;Jupyter notebooks with some number crunching&lt;/li&gt;
&lt;li&gt;No UI. No backend. Just the idea and the data analysis.&lt;/li&gt;
&lt;li&gt;"I'll build the rest... later"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After (product mode):&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Contract-first OpenAPI specs before any implementation&lt;/li&gt;
&lt;li&gt;Proper microservice boundaries with clear domain ownership&lt;/li&gt;
&lt;li&gt;Real TypeScript throughout — strict mode, no &lt;code&gt;any&lt;/code&gt;, proper DTOs&lt;/li&gt;
&lt;li&gt;Shared event contracts so every service speaks the same language&lt;/li&gt;
&lt;li&gt;End-to-end Docker setup so anyone can run it with one command&lt;/li&gt;
&lt;li&gt;Offline-first PWA with Workbox service workers, not just a checkbox&lt;/li&gt;
&lt;li&gt;A prediction engine that actually reflects the model weights from my notebooks&lt;/li&gt;
&lt;li&gt;Real Australian government services and grants seeded into the recommendation service&lt;/li&gt;
&lt;li&gt;Full lucide-react icon system, proper design tokens, accessible UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the biggest architectural calls was how to handle the shared module. Every service needed the same event shapes, DTOs, and logger. Instead of duplicating types everywhere (the hackathon approach), I built &lt;code&gt;@haven/shared&lt;/code&gt; as a proper local npm package that each service depends on. The Dockerfiles build it first, then link it. Small thing, but it's the kind of thing that makes a codebase actually maintainable.&lt;/p&gt;

&lt;p&gt;Another big one was the prediction engine. My XGBoost notebooks had a trained model for fire risk prediction. And here I'll be fully honest: I'm not a data scientist. During the hackathon I didn't really know what I was doing with the notebooks. I just sort of... cooked up weights that felt plausible. Temperature matters a lot? Sure, give it 0.35. Wind is bad? 0.25. Humidity inverse, obviously. Slap some season bonuses on. Done. Ship it.&lt;/p&gt;

&lt;p&gt;Copilot called this out completely. Not in a confrontational way — but when I started asking about the prediction engine during the rebuild, it surfaced the actual problems: the &lt;code&gt;test_size=0.8&lt;/code&gt; split that meant the model was &lt;em&gt;training on 20%&lt;/em&gt; of the data and validating on 80% (backwards), the &lt;code&gt;area_ha &amp;lt; 3&lt;/code&gt; outlier filter that was literally removing all the dangerous large fires from the training set, and — most damningly — the fact that the XGBoost model was predicting &lt;em&gt;fire size&lt;/em&gt; from historical boundary features while the TypeScript engine was computing a &lt;em&gt;risk score&lt;/em&gt; from weather inputs. They were solving completely different problems. The weights I'd put in had no relationship to anything the notebook had analysed. They were just numbers I made up at 2am that sounded reasonable.&lt;/p&gt;

&lt;p&gt;So instead of just transcribing invented numbers, I rebuilt it properly. The engine now implements the McArthur Forest Fire Danger Index (FFDI) Mark 5 — the actual formula used by the Bureau of Meteorology and every Australian state fire service. The notebook was fixed (correct 80/20 train/test split, log-IQR outlier handling to keep the large fires), and the XGBoost model now serves a real purpose: predicting expected fire size (in hectares) for a given state and month, used as a historical context signal blended at 20% weight into the FFDI weather score. The XGBoost model now runs as a proper Python &lt;code&gt;model-service&lt;/code&gt; FastAPI sidecar, trained at Docker build time from the GA Historical Bushfire Boundaries dataset and baked into the image so startup is instant. The prediction service calls it asynchronously with a 2-second timeout; if it's unreachable, FFDI-only scoring kicks in — no hard dependency on the critical path.&lt;/p&gt;

&lt;p&gt;The "I just made numbers up" moment is a good illustration of why having something that can actually interrogate your assumptions matters, especially when you're working outside your domain.&lt;/p&gt;

&lt;p&gt;The whole thing went from "hackathon skeleton with mock data" to a working end-to-end system you can actually spin up and test. Weather event goes in, prediction comes out, alert fires, PWA displays it — all observable, all local, all real.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;I used GitHub Copilot as the primary engineering partner throughout this comeback — not as an autocomplete tool, but as a &lt;strong&gt;system-aware collaborator that held the full architecture in context while I worked&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting the stage: copilot-instructions.md
&lt;/h3&gt;

&lt;p&gt;The first thing I did before writing a single line of implementation was write &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt; — a detailed spec covering the service boundaries, event contract shapes, CQRS split, security requirements, and UI/UX principles. Every rule I wanted enforced (contract-first OpenAPI, no &lt;code&gt;any&lt;/code&gt;, DTOs at boundaries, observability hooks, WCAG AA) went in there.&lt;/p&gt;

&lt;p&gt;That file became the foundation everything else was built on. Copilot read it before every response and generated code that conformed to patterns I'd already established — not guessing at conventions, but following documented ones. The difference in output quality was significant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffolding 6 microservices from a shared pattern
&lt;/h3&gt;

&lt;p&gt;The biggest single time-save was scaffolding all six backend services. Each needed the same structure: Express app, &lt;code&gt;helmet&lt;/code&gt; + &lt;code&gt;cors&lt;/code&gt; + &lt;code&gt;morgan&lt;/code&gt;, a rate limiter, a structured logger from &lt;code&gt;@haven/shared&lt;/code&gt;, a postgres schema init with seed data, RabbitMQ pub/sub wiring, health endpoints, and a multi-stage Dockerfile that builds &lt;code&gt;@haven/shared&lt;/code&gt; first and links it.&lt;/p&gt;

&lt;p&gt;That's probably 150 lines of consistent boilerplate per service. With Copilot understanding the pattern from the first service, each subsequent one took minutes rather than hours — and they were consistent in ways that matter: same error envelope shape, same correlation ID middleware, same health check format, same log field names.&lt;/p&gt;

&lt;h3&gt;
  
  
  The shared module problem
&lt;/h3&gt;

&lt;p&gt;Every service needed the same event shapes, DTOs, and logger. The hackathon approach was to duplicate them. The right approach was &lt;code&gt;@haven/shared&lt;/code&gt; — a local npm package with its own &lt;code&gt;tsconfig&lt;/code&gt;, built first in Docker, then installed into each service via &lt;code&gt;file:&lt;/code&gt; resolution.&lt;/p&gt;

&lt;p&gt;Working out the correct multi-stage Dockerfile pattern for this (build shared → copy dist → npm install in service context) is fiddly. Copilot got it right on the first pass and applied it consistently across all six service Dockerfiles and the Docker Compose dependency graph.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transcribing XGBoost weights into TypeScript
&lt;/h3&gt;

&lt;p&gt;My GovHack notebooks had a trained XGBoost model for fire risk prediction. Rather than running a Python sidecar in Docker, I wanted the prediction logic native to the Node service — self-contained, no cross-language IPC.&lt;/p&gt;

&lt;p&gt;I showed Copilot the notebook's feature importances and coefficient output. It generated a TypeScript heuristic engine that replicated the same decision logic: weighted inputs for temperature, humidity, wind speed, vegetation density, and season; thresholds mapped to severity levels; confidence intervals from the training accuracy. Same model, different runtime. The prediction output matched what the notebook produced for the same inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rebuilding the entire UI from scratch
&lt;/h3&gt;

&lt;p&gt;Emergency UX has specific demands — high contrast, large touch targets, one-handed mobile use, offline indicators, priority-tiered alert behaviour. None of the original components was built with any of that in mind.&lt;/p&gt;

&lt;p&gt;Copilot rebuilt the entire frontend systematically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design system&lt;/strong&gt; — a new &lt;code&gt;index.css&lt;/code&gt; with semantic CSS variables (&lt;code&gt;--c-critical&lt;/code&gt;, &lt;code&gt;--c-high&lt;/code&gt;, &lt;code&gt;--c-medium&lt;/code&gt;, &lt;code&gt;--c-safe&lt;/code&gt;, spacing tokens, radius tokens, &lt;code&gt;--nav-w&lt;/code&gt; for sidebar width, &lt;code&gt;--header-h&lt;/code&gt; for page header clearance) and glassmorphism card styles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Left sidebar nav&lt;/strong&gt; — rebuilt navigation from a mobile bottom bar to a 220px fixed left sidebar with icon + label rows, an active state indicator bar, online/offline status dot, and AI assistant + Settings links pinned at the bottom; collapses to a 60px icon-only rail at &lt;code&gt;&amp;lt; 768px&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page headers with back navigation&lt;/strong&gt; — every screen except the dashboard has a sticky &lt;code&gt;52px&lt;/code&gt; page header with a &lt;code&gt;ChevronLeft&lt;/code&gt; back button that calls &lt;code&gt;navigate(-1)&lt;/code&gt;, giving the app proper browser-history navigation without a router-level back stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web app layout&lt;/strong&gt; — removed the &lt;code&gt;430px&lt;/code&gt; phone-shell constraint and switched the root layout to &lt;code&gt;display: flex; flex-direction: row&lt;/code&gt; so the sidebar and content fill the viewport side by side; no &lt;code&gt;max-width&lt;/code&gt; on the main content area&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emergency dashboard&lt;/strong&gt; — risk severity banner with animated pulse on CRITICAL, two-column grid layout (map + stat tiles on the left, live alerts + nearest safe spaces on the right at &lt;code&gt;380px&lt;/code&gt;), sticky CTA buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert overlay&lt;/strong&gt; — full-screen takeover for CRITICAL priority alerts with haptic-style dismiss, priority-tiered behaviour (CRITICAL → full screen, HIGH → persistent, MEDIUM → toast, LOW → feed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three new screens&lt;/strong&gt; — Onboarding (4-step permissions flow), Settings (toggles, emergency contacts, accessibility), AI Assistant (chat interface — now backed by Nous Hermes 2 via Ollama)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All components used the design token system consistently — no hardcoded hex values, no magic numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atlas data enrichment and real open data
&lt;/h3&gt;

&lt;p&gt;The static seed data was a starting point, not a destination. I wanted the platform to pull from the best available Australian open data sources at startup — live fire detections, real evacuation assembly points, current government grants.&lt;/p&gt;

&lt;p&gt;Copilot initially wrote three &lt;code&gt;atlasEnrich.ts&lt;/code&gt; files using Geoscience Australia's Digital Atlas ArcGIS REST endpoints — a good foundation. But GA polygon data only goes so far, especially for fire detection where you want sub-3-hour satellite observations. So I went further.&lt;/p&gt;

&lt;p&gt;The final enrichment pipeline by service:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;safe-space-service&lt;/strong&gt; — GA Atlas (topographic facilities) → &lt;strong&gt;OpenStreetMap Overpass API&lt;/strong&gt; (no key required). The Overpass query targets &lt;code&gt;emergency=assembly_point&lt;/code&gt;, &lt;code&gt;emergency=evacuation_point&lt;/code&gt;, and &lt;code&gt;amenity=evacuation_centre&lt;/code&gt; nodes within the Australia bounding box. OSM has surprisingly comprehensive coverage of designated emergency assembly points that don't appear in government datasets. Accessibility tags (&lt;code&gt;wheelchair&lt;/code&gt;, &lt;code&gt;toilets_wheelchair&lt;/code&gt;, &lt;code&gt;parking_disabled&lt;/code&gt;, &lt;code&gt;pets&lt;/code&gt;) are read directly from OSM node tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prediction-service&lt;/strong&gt; — A four-source priority cascade: &lt;strong&gt;NASA FIRMS VIIRS NRT satellite detections&lt;/strong&gt; (3-hour near-real-time, requires a free MAP_KEY) → &lt;strong&gt;VIC Emergency public JSON feed&lt;/strong&gt; (no key) → &lt;strong&gt;NSW RFS major incidents JSON feed&lt;/strong&gt; (no key) → GA vegetation polygon centroids → static fallback. The FIRMS response includes fire radiative power (&lt;code&gt;frp&lt;/code&gt;) in MW and string confidence values (&lt;code&gt;'l'&lt;/code&gt;, &lt;code&gt;'n'&lt;/code&gt;, &lt;code&gt;'h'&lt;/code&gt;) which map to numeric probabilities for the risk engine. If FIRMS data exists, the live state feeds supplement it. If nothing is reachable, the static seed ensures the service always starts with plausible data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;recommendation-service&lt;/strong&gt; — Curated seed of known recovery programs → GA Atlas community facilities → &lt;strong&gt;GrantConnect API&lt;/strong&gt; (&lt;code&gt;/v2/grants?keyword=bushfire+disaster&amp;amp;status=open&lt;/code&gt;, requires free API key). GrantConnect is the Australian Government's official grants database. The integration maps grant categories to the service's scenario taxonomy (&lt;code&gt;GRANT&lt;/code&gt;, &lt;code&gt;RECOVERY&lt;/code&gt;, &lt;code&gt;HOUSING&lt;/code&gt;, &lt;code&gt;HEALTH&lt;/code&gt;, &lt;code&gt;EMERGENCY&lt;/code&gt;) and deduplicates on &lt;code&gt;(scenario, title)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All three are fire-and-forget calls in each service's &lt;code&gt;start()&lt;/code&gt; — never blocking, never throwing, always falling back gracefully. Both API keys (&lt;code&gt;NASA_FIRMS_MAP_KEY&lt;/code&gt;, &lt;code&gt;GRANTCONNECT_API_KEY&lt;/code&gt;) are optional environment variables documented in &lt;code&gt;.env.example&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging the ON CONFLICT DO NOTHING silent failure
&lt;/h3&gt;

&lt;p&gt;After wiring up the enrichment layer, the recommendation and safe-space services kept inserting duplicate rows on every restart. The &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt; clause was there — visually it looked correct. But it silently did nothing.&lt;/p&gt;

&lt;p&gt;The root cause: every INSERT was using &lt;code&gt;gen_random_uuid()&lt;/code&gt; as the primary key. UUID PKs are always unique by definition, so &lt;code&gt;ON CONFLICT&lt;/code&gt; on the PK could never trigger. There was no other unique constraint, so duplicates streamed in on every startup.&lt;/p&gt;

&lt;p&gt;The fix was two-part: add &lt;code&gt;UNIQUE&lt;/code&gt; constraints on natural keys (&lt;code&gt;name&lt;/code&gt; for safe spaces, &lt;code&gt;(scenario, title)&lt;/code&gt; for recommendations) idempotently in the service's &lt;code&gt;init()&lt;/code&gt; via a &lt;code&gt;DO $$ BEGIN ... EXCEPTION WHEN duplicate_table THEN NULL; END $$&lt;/code&gt; block, then update every INSERT — both seed and enrichment — to specify the column: &lt;code&gt;ON CONFLICT (name) DO NOTHING&lt;/code&gt;. Copilot caught that the same pattern existed in all three &lt;code&gt;atlasEnrich.ts&lt;/code&gt; files and fixed them in one pass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging the amqplib type breaking change
&lt;/h3&gt;

&lt;p&gt;Mid-rebuild, four services stopped compiling. The &lt;code&gt;amqplib&lt;/code&gt; types had changed between versions — &lt;code&gt;Connection&lt;/code&gt; became &lt;code&gt;ChannelModel&lt;/code&gt;, and import paths shifted. The errors were spread across &lt;code&gt;bus.ts&lt;/code&gt; in every service that used the message broker.&lt;/p&gt;

&lt;p&gt;Rather than hunting them down one by one, Copilot identified the pattern across all four files at once and applied the fix consistently: updated import paths, replaced &lt;code&gt;Connection&lt;/code&gt; with &lt;code&gt;ChannelModel&lt;/code&gt;, added the &lt;code&gt;.channel&lt;/code&gt; access where the API had changed. What would have been 20 minutes of grep-and-fix was one operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  The quality multiplier
&lt;/h3&gt;

&lt;p&gt;The pattern I noticed across all of this: &lt;strong&gt;Copilot's output quality scaled directly with the quality of my specifications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vague prompt → vague result. But when the architecture was documented, the event contracts were defined, and the patterns were established in real code — Copilot could work within all of those constraints simultaneously. It wasn't generating generic Express boilerplate. It was generating Express code that matched the correlation ID middleware pattern, used the shared logger with the right field names, published events with the correct envelope shape, and returned errors in the &lt;code&gt;{ code, message }&lt;/code&gt; format defined in the OpenAPI spec.&lt;/p&gt;

&lt;p&gt;That's the real leverage. Not autocomplete. &lt;strong&gt;Specification → conforming implementation at speed.&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;There's a whole graveyard of hackathon projects in GitHub repositories everywhere.&lt;/p&gt;

&lt;p&gt;Most of them deserved more time than they got.&lt;/p&gt;

&lt;p&gt;Project Haven started as a 46-hour sprint, won a competition, and then sat untouched for almost two years. This challenge gave me the push to go back and ask: what would this actually look like if it was built properly?&lt;/p&gt;

&lt;p&gt;The answer turned out to be: a lot better.&lt;/p&gt;

&lt;p&gt;The prediction pipeline works end-to-end. The offline-first architecture actually functions. The microservices actually talk to each other through real events. The UI is a full-width web application — not a phone simulator in a browser — with proper sidebar navigation, back-button routing, and a two-column dashboard that uses screen real estate the way a desktop tool should. Live fire detections from NASA satellites, real Australian evacuation assembly points from OpenStreetMap, and current government grants from GrantConnect flow in at startup. The AI Assistant — previously a &lt;code&gt;setTimeout&lt;/code&gt; and a &lt;code&gt;switch&lt;/code&gt; statement of canned responses — is now backed by Nous Hermes 2 running locally via Ollama, with a system prompt grounded in verified Australian emergency protocols. The prediction engine now implements the McArthur FFDI Mark 5 formula (the actual BOM standard) blended with an XGBoost model trained from real GA historical fire data — running as a Python FastAPI sidecar built into Docker — replacing the made-up weights from the hackathon. The whole thing runs with a single &lt;code&gt;docker compose up --build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;None of that would have happened at this pace working solo without Copilot. Not because any individual piece was impossible — but because the sheer volume of consistent, conforming implementation across six services, a shared module, a full UI rebuild, a data enrichment layer across four distinct external APIs, and edge cases like the &lt;code&gt;ON CONFLICT&lt;/code&gt; silent failure would have taken weeks of context-switching. With Copilot holding the system context and working within the established patterns, it compressed into days.&lt;/p&gt;

&lt;p&gt;I'm not saying it's done. There's still proper auth flows, load testing, mobile push notifications, and a dozen other things on the list.&lt;/p&gt;

&lt;p&gt;But it's no longer frozen in time as a hackathon demo.&lt;/p&gt;

&lt;p&gt;It feels like something that could actually help someone.&lt;/p&gt;

&lt;p&gt;And that's probably the best thing this challenge could have done.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with TypeScript, React, Node.js, Python (FastAPI + XGBoost), PostgreSQL, RabbitMQ, Docker, Ollama (Nous Hermes 2), and GitHub Copilot.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Originally born at GovHack 2024. Finally given room to grow.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Built a Multi-Agent AI Tribunal with Gemma 4</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Fri, 15 May 2026 15:41:31 +0000</pubDate>
      <link>https://dev.to/ujja/i-built-a-multi-agent-ai-tribunal-with-gemma-4-3ood</link>
      <guid>https://dev.to/ujja/i-built-a-multi-agent-ai-tribunal-with-gemma-4-3ood</guid>
      <description>&lt;p&gt;What If AI Agents Put Each Other on Trial?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;HumanLayer is a multi-agent AI governance platform. It features specialised Gemma 4 agents that collaborate to review, challenge, and hold one another accountable. This approach differs from a single model that quietly makes all the decisions.&lt;/p&gt;

&lt;p&gt;Most governance tooling is written for people who already understand governance. I aimed to build a system that makes governance decisions understandable to the people they impact. I consider plain-English explainability a hard requirement, not simply a nice-to-have.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────────────┐
│                          Browser / Next.js 15 UI                            │
│          Upload Doc  ·  Start Tribunal  ·  Appeal  ·  Audit Trail           │
└────────────────────────────────┬────────────────────────────────────────────┘
                                 │  HTTPS + CSRF
┌────────────────────────────────▼────────────────────────────────────────────┐
│                        FastAPI Backend  (port 8000)                         │
│  REST API v1  ·  JWT Auth  ·  Rate Limiting  ·  Content Scan Middleware     │
│                                                                              │
│  ┌─────────────────────────┐     ┌──────────────────────────────────────┐  │
│  │   Governance Council    │     │       Constitutional Tribunal        │  │
│  │                         │     │                                      │  │
│  │  Security  ·  Ethics    │     │  Prosecutor  ·  Defender             │  │
│  │  Privacy   ·  Access.   │     │  Advocate    ·  Ethics               │  │
│  │  Audit                  │     │       ↓  3 debate rounds             │  │
│  │       ↓  parallel       │     │  AI Jury ×4  →  Governance Judge     │  │
│  │  Consensus Engine       │     │       ↓  constitutional ruling       │  │
│  └────────────┬────────────┘     └─────────────┬────────────────────────┘  │
│               └──────────────────┬──────────────┘                           │
│                                  │                                           │
│              ┌───────────────────▼──────────────────┐                       │
│              │          Celery Worker (async)        │                       │
│              │  tribunal · analysis · documents ·    │                       │
│              │  default queues · concurrency=2       │                       │
│              └───────────┬───────────────────────────┘                      │
└──────────────────────────┼──────────────────────────────────────────────────┘
                           │
          ┌────────────────┼────────────────────┐
          ▼                ▼                     ▼
┌──────────────┐  ┌─────────────────┐  ┌─────────────────────────────────┐
│  PostgreSQL  │  │  Redis (broker  │  │    Google AI Studio             │
│  (SQLAlchemy │  │  + result store)│  │                                 │
│   async)     │  └─────────────────┘  │  gemma-4-26b-a4b-it  (MoE)     │
│              │                       │  gemma-4-31b-it      (Dense)    │
│  Cases  ·    │  ┌─────────────────┐  │                                 │
│  Agents ·    │  │  Local Storage  │  │  ← resolves gemma4:2b/4b/9b/moe │
│  Audits ·    │  │  /uploads       │  │    → MoE at runtime             │
│  Precedents  │  └─────────────────┘  │  ← resolves gemma4:31b          │
└──────────────┘                       │    → Dense 31B at runtime       │
                                       └─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│              Observability  (OpenTelemetry → Prometheus → Grafana)          │
│              Dev Monitor on port 8001  ·  SSE log stream  ·  phase bars    │
└─────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The platform has two modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Governance Council&lt;/strong&gt; — Five agents check uploaded documents. They review policy docs, OAuth configs, onboarding flows, and architecture reports. They ensure compliance with GDPR, AI Act, WCAG 2.2, and ISO 27001 standards. They then use a consensus engine to create one governance verdict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constitutional Tribunal&lt;/strong&gt; — A full adversarial process: four agents present their cases in three debate rounds. An AI jury with four members checks the reasoning quality, and a Governance Judge delivers a constitutional ruling. The human can appeal and override at any point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────────────┐
│                        Constitutional Tribunal                                   │
│                                                                                  │
│  ┌─────────────────┐  ┌──────────────────────┐  ┌──────────────┐  ┌──────────┐ │
│  │ Security        │  │ Accessibility         │  │ Privacy      │  │ Ethics   │ │
│  │ Prosecutor      │  │ Defender              │  │ Advocate     │  │ Council  │ │
│  │ gemma4:31b      │  │ gemma4:2b             │  │ gemma4:9b    │  │ gemma4:  │ │
│  │                 │  │ · WCAG 2.2 AA         │  │ · GDPR Art.7 │  │ 31b      │ │
│  │ · OWASP Top 10  │  │ · Flesch-Kincaid      │  │ · Data min.  │  │ · Bias   │ │
│  │ · STRIDE model  │  │ · Plain-English       │  │ · Consent    │  │   ×8     │ │
│  │ · RBAC/OAuth    │  │   rewrite             │  │   validity   │  │   classes│ │
│  └────────┬────────┘  └──────────┬────────────┘  └──────┬───────┘  └────┬─────┘ │
│           └───────────────────────┴──────────────────────┴───────────────┘       │
│                              Phase 1: Opening Arguments (concurrent)             │
│                              Phase 2: Cross-Examination (each challenges all)    │
│                              Phase 3: Closing Arguments (full history injected)  │
│                                              │                                   │
│                              ┌───────────────▼────────────────────────┐         │
│                              │  AI Jury Panel  (×4 independent)       │         │
│                              │  gemma4:moe                            │         │
│                              │  · Evidence validity check             │         │
│                              │  · Logical consistency score           │         │
│                              │  · Hallucination risk flag             │         │
│                              │  · Constitutional alignment            │         │
│                              └───────────────┬────────────────────────┘         │
│                              Phase 4: Jury Deliberation + consensus score        │
│                              Phase 5: Trust scores + governance DSL rules        │
│                                              │                                   │
│                              ┌───────────────▼────────────────────────┐         │
│                              │  Governance Judge                      │         │
│                              │  gemma4:moe                            │         │
│                              │  approve · reject · escalate ·         │         │
│                              │  conditional (with remediation steps)  │         │
│                              └───────────────┬────────────────────────┘         │
│                              Phase 6: Constitutional Ruling                      │
│                                              │                                   │
│               ┌──────────────────────────────┼──────────────────────────┐       │
│               ▼                              ▼                          ▼       │
│      ┌────────────────┐          ┌───────────────────┐       ┌─────────────────┐│
│      │  Audit Agent   │          │  Human Appeal &amp;amp;   │       │  Precedent      ││
│      │  gemma4:4b     │          │  Override         │       │  Library        ││
│      │  Immutable     │          │  always available │       │  high-confidence││
│      │  audit trail   │          │  at any phase     │       │  cases stored   ││
│      └────────────────┘          └───────────────────┘       └─────────────────┘│
│      Phase 7: Audit + Precedent                                                  │
└──────────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tribunal case runs 7 ordered phases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Opening Arguments&lt;/td&gt;
&lt;td&gt;All 4 adversarial agents argue concurrently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Cross-Examination&lt;/td&gt;
&lt;td&gt;Each agent challenges the others' positions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Closing Arguments&lt;/td&gt;
&lt;td&gt;Final positions with full debate history injected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Jury Deliberation&lt;/td&gt;
&lt;td&gt;4 independent validators score reasoning quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Trust + DSL Evaluation&lt;/td&gt;
&lt;td&gt;Trust scores updated; governance rules evaluated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6. Constitutional Ruling&lt;/td&gt;
&lt;td&gt;Judge issues approve/reject/escalate / conditional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7. Audit + Precedent&lt;/td&gt;
&lt;td&gt;Immutable audit written; high-confidence cases become precedents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Minority opinions are preserved at every phase. The human can appeal and override at any point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No agent can approve its own actions. Every verdict is traceable to a specific rationale chain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack&lt;/strong&gt;: FastAPI + Celery + Redis · Next.js 15 · Google AI Studio (Gemma 4) · OpenTelemetry + Prometheus&lt;/p&gt;

&lt;h3&gt;
  
  
  Case Simulations
&lt;/h3&gt;

&lt;p&gt;The platform ships with 8 pre-built governance scenarios. Here are the most illustrative ones:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 1 — Inaccessible MFA Crisis&lt;/strong&gt; &lt;em&gt;(accessibility vs security)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A bank mandates CAPTCHA + MFA after brute-force attacks. Security Prosecutor (&lt;code&gt;gemma4:31b&lt;/code&gt;) builds a rigorous OWASP case. Accessibility Defender (&lt;code&gt;gemma4:2b&lt;/code&gt;) flags that the CAPTCHA does not meet WCAG 2.2 SC 1.1.1. Also, the MFA UX creates cognitive barriers for users with anxiety. Ethics Council (&lt;code&gt;gemma4:31b&lt;/code&gt;) adds intersectional impact — disabled users are disproportionately locked out. The Jury detects that Security's argument implicitly assumes able-bodied users; consensus drops. Verdict: conditional approval — passkey auth with accessible fallback required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 2 — Rogue Agent Self-Approval&lt;/strong&gt; &lt;em&gt;(constitutional violation)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An autonomous deployment agent tries to approve its own production push. This is a clear violation of the separation of duties. Security Prosecutor traces the approval chain; Audit Agent reconstructs the hidden trust-score history. The Judge checks three governance DSL rules and gives a hard reject without needing a jury. The constitutional violation is clear. This case is specifically designed to test the "no agent can approve its own actions" invariant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 3 — Manipulative Consent Flow&lt;/strong&gt; &lt;em&gt;(dark pattern detection)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A SaaS onboarding screen checks tracking consent. It hides the opt-out option three layers deep. Urgent language is used. Accessibility Defender (&lt;code&gt;gemma4:2b&lt;/code&gt;) has a Grade 14 Flesch-Kincaid score. It warns about cognitive overload. Privacy Advocate (&lt;code&gt;gemma4:9b&lt;/code&gt;) focuses on GDPR Article 7. It argues that pre-checked boxes do not give real consent. Ethics Council points out predatory targeting of low-literacy and elderly users. Verdict: reject. The Accessibility Agent rewrites the consent copy as a remediation artefact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 4 — Discriminatory Hiring Pipeline&lt;/strong&gt; &lt;em&gt;(AI bias review)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An AI hiring system rejects candidates from certain demographic groups at higher rates. The Ethics Council (&lt;code&gt;gemma4:31b&lt;/code&gt;) conducts intersectional analysis across 8 protected classes. It shows that resume formatting choices can reflect socioeconomic status. This can lead to proxy discrimination. Verdict: escalated — cannot approve or reject without a third-party fairness audit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case 5 — Translation Drift Crisis&lt;/strong&gt; &lt;em&gt;(multilingual governance failure)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A governance policy is translated into six languages. This change quietly affects its legal meaning in two of them. The term "data minimisation" becomes "data reduction" in one area. This leads to different legal implications. The Jury (&lt;code&gt;gemma4:moe&lt;/code&gt;) uses Gemma 4's long-context window. This helps it check semantic consistency in the entire translation diffs. Ethics Council (&lt;code&gt;gemma4:31b&lt;/code&gt;) identifies that the error would have changed user rights without notice. Verdict: conditional approval — re-translation with legal review required. This case shows why context window size is key. A text-snippet method would miss the cross-locale semantic drift. It wouldn't capture the differences at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;The main architectural choice was deciding which Gemma 4 variant to use for each role. This is important because each reasoning task needs a different model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model-per-role design
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Canonical model&lt;/th&gt;
&lt;th&gt;Why this size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility Agent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:2b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fast pattern recognition: reading level, WCAG checks, plain-English rewrites. Speed and empathy over depth.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit Agent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:4b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Neutral summarisation and multi-agent narrative coherence. Slightly more capable than 2b for timeline reconstruction.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy Advocate&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:9b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nuanced consent analysis across three adversarial debate rounds. Needs legal depth without the latency of 31b.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Agent&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Context-dense threat modelling — holds full RBAC configs, OAuth flows, and STRIDE analysis simultaneously.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ethics &amp;amp; Inclusion&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:31b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Intersectional bias reasoning across 8 protected classes. Detecting proxy discrimination requires the full model capacity.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governance Agent + Jury (×4) + Judge&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma4:moe&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Orchestration, meta-reasoning, cross-domain judgment. MoE's expert sub-networks activate per token type rather than averaging.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The canonical IDs are used throughout the codebase. Each inference backend turns them into physical model names at runtime. The agent code stays the same when you switch backends.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's actually running today
&lt;/h3&gt;

&lt;p&gt;In practice, only two Gemma 4 models are currently available via any working hosted API (Google AI Studio):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;&lt;/strong&gt; — Sparse MoE, 26B total / ~4B active params. Maps to the &lt;code&gt;2b&lt;/code&gt;, &lt;code&gt;4b&lt;/code&gt;, &lt;code&gt;9b&lt;/code&gt;, and &lt;code&gt;moe&lt;/code&gt; roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gemma-4-31b-it&lt;/code&gt;&lt;/strong&gt; — Dense 31B. Maps to security and ethics roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model-resolution maps in the backend (&lt;code&gt;_GOOGLE_AI_MODEL_MAP&lt;/code&gt;) bridge the gap. &lt;code&gt;gemma4:2b&lt;/code&gt; resolves to &lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; today and will resolve to the real 2b model the moment it's available — no code changes needed.&lt;/p&gt;

&lt;p&gt;Getting Gemma 4 running was a significant part of the project. Here's what I ran into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt;: Gemma 4 isn't in the registry yet (404 error).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HuggingFace Serverless&lt;/strong&gt;: Tried 7 providers; all returned "Model not supported".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle hosted inference&lt;/strong&gt;: No REST endpoint; token only allows downloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kaggle local download&lt;/strong&gt;: Works for &lt;code&gt;2b&lt;/code&gt;/&lt;code&gt;4b&lt;/code&gt; but CPU inference takes 30–120s per call — impractical for 10–15 agent calls per tribunal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AI Studio&lt;/strong&gt; — free API key, OpenAI-compatible endpoint, 1,500 req/day — was the one that worked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why the model-sizing decisions matter
&lt;/h3&gt;

&lt;p&gt;Using the sparse MoE model for accessibility and the dense 31B for security isn't just a cost optimisation. It reflects a genuine difference like those reasoning tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt; (MoE)&lt;/strong&gt; — The Accessibility Agent quickly spots patterns with empathy. It estimates the Flesch-Kincaid reading level, checks for WCAG 2.2 AA compliance, finds shame-based error messages, and rewrites hostile content into plain English suitable for dyslexia at the Grade 8 level. These tasks don't require deep cross-referenced reasoning. They require speed and consistency. Sparse activation (~4B params per token) is exactly right.&lt;/p&gt;

&lt;p&gt;The same model handles the Audit Agent (neutral timeline reconstruction), Privacy Advocate (consent analysis across debate rounds), and the full Jury Panel + Governance Judge (meta-reasoning, cross-domain orchestration). MoE's expert sub-network routing means different token types activate different specialists rather than averaging across all domains simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;gemma-4-31b-it&lt;/code&gt; (Dense 31B)&lt;/strong&gt; — The Security Agent must manage the full RBAC setup, OAuth token flow, redirect URIs, and STRIDE threat model all at once. The Ethics Agent must consider 8 protected classes. They look for proxy discrimination when a policy seems neutral but harms a protected group. Both tasks require the kind of multi-document, cross-referenced reasoning that the dense 31B handles substantially better than smaller variants.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Gemma 4 capabilities that made this possible
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Long-context window.&lt;/strong&gt; A single tribunal case feeds the full debate history (all prior round outputs) into each jury agent. Without a long-context window, jury agents would miss the cross-examination context. This context is key for evaluating reasoning quality effectively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodal input.&lt;/strong&gt; A significant portion of governance artefacts aren't text — they're screenshots of onboarding flows, consent screens, and admin dashboards. Agents can check visual accessibility patterns and CAPTCHA flows. They can also spot UI governance risks. A text-only model would miss these details. The multilingual governance simulation does this: agents look at screenshots of translated policy text side by side. They check for layout differences. These differences can affect readability in different places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reasoning mode.&lt;/strong&gt; The &lt;code&gt;&amp;lt;thought&amp;gt;&lt;/code&gt; tags in Gemma 4's output are removed before parsing the JSON response. However, the reasoning process is key to the quality of analysis in complex cases. This is especially true for detecting jury hallucinations and checking constitutional alignment.&lt;/p&gt;

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

&lt;p&gt;Disagreement is a feature, not a bug. Most AI systems optimise for confident, singular answers. HumanLayer deliberately surfaces disagreement — between agents, across debate rounds, in the audit trail. That visibility turns out to be the most useful part, because it shows users why a decision landed where it did.&lt;/p&gt;

&lt;p&gt;When smaller Gemma 4 variants become available via the API, the per-role assignment will get even more precise. The architecture is already waiting for them.&lt;/p&gt;

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

&lt;p&gt;Note: The app currently runs locally.&lt;br&gt;
Demo video URL: &lt;a href="https://www.youtube.com/watch?v=pfUncccezQA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=pfUncccezQA&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.amazonaws.com%2Fuploads%2Farticles%2Fni99ex8lmkkzth4v8qmc.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%2Fni99ex8lmkkzth4v8qmc.png" alt="Constitutional Proceeding"&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.amazonaws.com%2Fuploads%2Farticles%2F5tv4bfuqikx30dourrum.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%2F5tv4bfuqikx30dourrum.png" alt="Opening Arguments"&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.amazonaws.com%2Fuploads%2Farticles%2F20xfmsodq4l767z2xlnr.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%2F20xfmsodq4l767z2xlnr.png" alt="Cross - Exam"&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.amazonaws.com%2Fuploads%2Farticles%2F4anoi0t8s28o9byzu81n.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%2F4anoi0t8s28o9byzu81n.png" alt="Agent Closing Argument"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Repository: &lt;a href="https://github.com/ujjavala/HumanLayer" rel="noopener noreferrer"&gt;https://github.com/ujjavala/HumanLayer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Architecture Docs: &lt;a href="https://github.com/ujjavala/HumanLayer/tree/main/docs" rel="noopener noreferrer"&gt;https://github.com/ujjavala/HumanLayer/tree/main/docs&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;One question stayed with me throughout this project:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If AI systems become influential enough to shape governance decisions, who governs the governors?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;HumanLayer is one answer: make the AI systems govern each other, transparently, with human override always available. Expose disagreement instead of hiding it. Treat accessibility as a governance requirement. Build audit trails that explain decisions to the people they affect, not just to the compliance team.&lt;/p&gt;

&lt;p&gt;Trustworthy AI will probably look less like all-knowing superintelligence and more like collaborative systems designed to keep each other accountable.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
      <category>agents</category>
    </item>
    <item>
      <title>I Rethought My Sustainability App After Google Cloud Next ’26 — and my architecture broke (in a good way)</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Mon, 27 Apr 2026 08:04:12 +0000</pubDate>
      <link>https://dev.to/ujja/i-rethought-planetledger-after-google-cloud-next-2026-and-my-architecture-broke-in-a-good-way-glp</link>
      <guid>https://dev.to/ujja/i-rethought-planetledger-after-google-cloud-next-2026-and-my-architecture-broke-in-a-good-way-glp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-cloud-next-2026-04-22"&gt;Google Cloud NEXT Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few days ago, I shipped &lt;strong&gt;&lt;a href="https://dev.to/ujja/planetledger-turning-spending-into-environmental-awareness-4b8f"&gt;PlanetLedger&lt;/a&gt;&lt;/strong&gt; — a weekend hackathon project that turns bank transactions into environmental impact insights.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;an event-driven pipeline (OpenClaw)
&lt;/li&gt;
&lt;li&gt;an agent layer with memory
&lt;/li&gt;
&lt;li&gt;RAG-grounded insights
&lt;/li&gt;
&lt;li&gt;deterministic scoring + AI fallback
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On paper, it looks like a modern AI system.&lt;/p&gt;

&lt;p&gt;Then I watched Google Cloud NEXT ‘26.&lt;/p&gt;

&lt;p&gt;And something didn’t sit right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable realisation
&lt;/h2&gt;

&lt;p&gt;While going through the announcements —&lt;br&gt;&lt;br&gt;
the &lt;strong&gt;Gemini Enterprise Agent Platform&lt;/strong&gt;, &lt;strong&gt;Agentic Data Cloud&lt;/strong&gt;, and long-running autonomous agents — I had this thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I didn’t build an AI system.&lt;br&gt;&lt;br&gt;
I built a pipeline that treats AI as the final step — not the decision-maker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds subtle.&lt;/p&gt;

&lt;p&gt;It’s not.&lt;/p&gt;

&lt;h2&gt;
  
  
  My architecture (before NEXT)
&lt;/h2&gt;

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

&lt;p&gt;PlanetLedger today works like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;upload → parse → categorise → score → build context → generate insights → notify  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Internally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;event → &lt;a href="https://dev.to/ujja/i-built-my-own-event-bus-for-a-sustainability-app-heres-what-i-learned-about-agent-automation-2cfl"&gt;OpenClaw → workflows&lt;/a&gt; → AI → UI  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s clean, predictable, and works well.&lt;/p&gt;

&lt;p&gt;But it’s also:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;fully deterministic until the very last step  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI is where the pipeline ends — not where decisions begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 What NEXT ‘26 changes
&lt;/h2&gt;

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

&lt;p&gt;The biggest shift across announcements wasn’t better models.&lt;/p&gt;

&lt;p&gt;It was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;We’ve entered the agentic era.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI is no longer something you call.&lt;/p&gt;

&lt;p&gt;It’s something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;acts
&lt;/li&gt;
&lt;li&gt;reasons over data
&lt;/li&gt;
&lt;li&gt;runs workflows autonomously
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three announcements made that click for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Gemini Enterprise Agent Platform&lt;/strong&gt; → build and scale real agents
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Agentic Data Cloud&lt;/strong&gt; → agents reason directly over structured data
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-running agents in serverless environments&lt;/strong&gt; → agents don’t just respond, they operate
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this actually looks like on Google Cloud
&lt;/h2&gt;

&lt;p&gt;Mapping my system to Google Cloud made the gap obvious:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I built&lt;/th&gt;
&lt;th&gt;Google Cloud direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/ujja/i-built-my-own-event-bus-for-a-sustainability-app-heres-what-i-learned-about-agent-automation-2cfl"&gt;OpenClaw event triggers&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Pub/Sub / Eventarc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardcoded workflows&lt;/td&gt;
&lt;td&gt;Workflows / agent execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG context builder&lt;/td&gt;
&lt;td&gt;Agentic Data Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM calls for insights&lt;/td&gt;
&lt;td&gt;Gemini agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron-based automation&lt;/td&gt;
&lt;td&gt;Long-running autonomous agents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;What I built locally is essentially a &lt;strong&gt;proto-version of a cloud-native agent system — but missing the intelligence layer at the core&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reimagining PlanetLedger
&lt;/h2&gt;

&lt;p&gt;So I asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if PlanetLedger wasn’t a pipeline… but an agent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. Events → from triggers to signals
&lt;/h3&gt;

&lt;p&gt;Today:&lt;br&gt;
transactions_uploaded → trigger workflows&lt;/p&gt;

&lt;p&gt;In an agent-first system:&lt;br&gt;
transactions_uploaded → agent decides what to do&lt;/p&gt;

&lt;p&gt;Same event.&lt;br&gt;&lt;br&gt;
Completely different meaning.&lt;/p&gt;

&lt;p&gt;Events stop being instructions.&lt;/p&gt;

&lt;p&gt;They become &lt;strong&gt;inputs for reasoning&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pipeline → replaced by a Financial Agent
&lt;/h3&gt;

&lt;p&gt;Today, I explicitly define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parse
&lt;/li&gt;
&lt;li&gt;categorise
&lt;/li&gt;
&lt;li&gt;score
&lt;/li&gt;
&lt;li&gt;generate insights
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an agent-based system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Financial Agent:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;understands transactions
&lt;/li&gt;
&lt;li&gt;detects patterns
&lt;/li&gt;
&lt;li&gt;decides what matters
&lt;/li&gt;
&lt;li&gt;chooses actions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“run this sequence”&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;“figure out what needs to happen”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. RAG → becomes native data reasoning
&lt;/h3&gt;

&lt;p&gt;Right now, I manually construct context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;last 7 days
&lt;/li&gt;
&lt;li&gt;top categories
&lt;/li&gt;
&lt;li&gt;detected patterns
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then inject it into prompts.&lt;/p&gt;

&lt;p&gt;With something like the &lt;strong&gt;Agentic Data Cloud&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the agent queries data directly
&lt;/li&gt;
&lt;li&gt;builds its own context
&lt;/li&gt;
&lt;li&gt;adapts dynamically
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Less glue code. More intelligence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Workflows → become optional
&lt;/h3&gt;

&lt;p&gt;OpenClaw is intentionally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sequential
&lt;/li&gt;
&lt;li&gt;deterministic
&lt;/li&gt;
&lt;li&gt;easy to debug
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it’s still:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;explicit orchestration  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The direction from NEXT suggests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long-running agents
&lt;/li&gt;
&lt;li&gt;dynamic tool usage
&lt;/li&gt;
&lt;li&gt;adaptive execution paths
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;step A → step B → step C  &lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;goal → agent decides steps → executes tools  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More powerful.&lt;br&gt;&lt;br&gt;
Also harder to control.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The new problem: trust
&lt;/h3&gt;

&lt;p&gt;In my current system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI generates insights
&lt;/li&gt;
&lt;li&gt;but doesn’t act
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an agent system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI can trigger workflows
&lt;/li&gt;
&lt;li&gt;influence decisions
&lt;/li&gt;
&lt;li&gt;shape outcomes
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which introduces something new:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You now need to trust your architecture — not just your code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validation layers
&lt;/li&gt;
&lt;li&gt;auditability
&lt;/li&gt;
&lt;li&gt;explainability
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially for something tied to financial behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real shift
&lt;/h2&gt;

&lt;p&gt;If I compress everything I learned into one line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I went from designing workflows → to designing decision boundaries&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Old vs New mental model
&lt;/h2&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PlanetLedger Today&lt;/th&gt;
&lt;th&gt;PlanetLedger (NEXT-style)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event triggers workflows&lt;/td&gt;
&lt;td&gt;Event triggers reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline-first&lt;/td&gt;
&lt;td&gt;Agent-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG context builder&lt;/td&gt;
&lt;td&gt;Native data reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic flow&lt;/td&gt;
&lt;td&gt;Adaptive execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insights&lt;/td&gt;
&lt;td&gt;Actions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I’d do next
&lt;/h2&gt;

&lt;p&gt;If I were to rebuild PlanetLedger today using these ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Introduce an agent layer using Gemini-style reasoning
&lt;/li&gt;
&lt;li&gt;Let the agent decide when to generate insights vs alerts
&lt;/li&gt;
&lt;li&gt;Replace static RAG with dynamic data querying
&lt;/li&gt;
&lt;li&gt;Add explainability for every AI-driven decision
&lt;/li&gt;
&lt;li&gt;Keep events — but demote them to signals, not drivers
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not because the current system is wrong.&lt;/p&gt;

&lt;p&gt;But because the direction is clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The future isn’t event-driven systems &lt;em&gt;with AI&lt;/em&gt;&lt;br&gt;&lt;br&gt;
It’s AI systems that &lt;em&gt;use events&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Google Cloud NEXT ‘26 didn’t just introduce new tools.&lt;/p&gt;

&lt;p&gt;It exposed a shift:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We’re moving from systems that process data&lt;br&gt;&lt;br&gt;
to systems that interpret and act on it&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PlanetLedger didn’t break after NEXT.&lt;/p&gt;

&lt;p&gt;But the way I think about building it did.&lt;/p&gt;

&lt;p&gt;And that’s a much bigger change.&lt;/p&gt;

&lt;p&gt;If you’ve built something similar — pipelines, workflows, event buses — try this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Remove the pipeline.&lt;br&gt;&lt;br&gt;
Replace it with an agent.&lt;br&gt;&lt;br&gt;
See what breaks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s probably where the next version lives.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>cloudnextchallenge</category>
      <category>googlecloud</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
