<?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 Asked 10 AI Models to Reconstruct Real Cyber Attacks</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Thu, 01 Oct 2026 15:33:59 +0000</pubDate>
      <link>https://dev.to/ujja/i-asked-10-ai-models-to-reconstruct-real-cyber-attacks-2o3j</link>
      <guid>https://dev.to/ujja/i-asked-10-ai-models-to-reconstruct-real-cyber-attacks-2o3j</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kaggle-2026-09-23"&gt;Kaggle Benchmarking Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Cyber incident reports rarely arrive as a clean, complete timeline. They describe what investigators or security teams could observe, mix those observations with analysis, and leave some questions unanswered. That is exactly where an AI-generated summary can become risky: it may sound convincing while quietly turning an inference into a fact or filling a gap with an event that the evidence never established.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Cyber Autopsy&lt;/strong&gt; to test a narrower, practical question: when given pieces of a reported cyber incident, can a model reconstruct what happened while showing which evidence supports each claim and admitting what remains uncertain? The model must produce a timeline, connect events with causal or temporal links, cite evidence, and distinguish confirmed activity from inference, failed attempts, contradictions, and unknowns. A plausible attack story is not enough; unsupported certainty should count against it.&lt;/p&gt;

&lt;p&gt;I chose this problem because incident reconstruction depends on more than recognizing familiar attack techniques. An analyst needs to know whether a step was observed or inferred, whether an attempt actually succeeded, and how one event led to another. Those distinctions can disappear in a fluent summary. Measuring them separately makes it easier to see whether a model is recovering the evidence or merely telling a likely-sounding story.&lt;/p&gt;

&lt;p&gt;The first evaluation uses seven tasks built from four public reports. They include a detailed human-operated ransomware intrusion and vendor-reported campaigns involving AI-assisted activity. These are useful real-world case studies, but they are not a controlled contest between human and AI attackers: the reports differ in detail, evidence source, and corroboration. I also included two versions of one case with identical evidence but different actor framing, to see whether that wording changes the model's reconstruction.&lt;/p&gt;

&lt;p&gt;This is a pilot, not a claim that AI attackers are more or less capable than people. It evaluates model reconstructions of reported incidents, not live attack behavior. The scorer is deterministic and reports an Evidence-Grounded Reconstruction Score (EGRS), alongside event recall and precision, causal-link quality, evidence attribution, status accuracy, uncertainty calibration, and hallucination-related measures. The temporal cutoff and framing pair are exploratory comparisons; only the framing pair holds the evidence fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-world case studies
&lt;/h3&gt;

&lt;p&gt;The seven tasks are built from four public incident reports, not invented scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RansomHub intrusion (CASE-001 and CASE-004):&lt;/strong&gt; &lt;a href="https://thedfirreport.com/2025/06/30/hide-your-rdp-password-spray-leads-to-ransomhub-deployment/" rel="noopener noreferrer"&gt;The DFIR Report's “Hide Your RDP: Password Spray Leads to RansomHub Deployment”&lt;/a&gt; describes a human-operated intrusion using password spraying and RDP, credential access, Rclone exfiltration, and eventual RansomHub deployment. CASE-004 reuses this incident but cuts off the evidence at the end of day one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTG-1002 espionage campaign (CASE-002, CASE-011, CASE-012):&lt;/strong&gt; Anthropic's &lt;a href="https://www.anthropic.com/news/disrupting-AI-espionage" rel="noopener noreferrer"&gt;incident report&lt;/a&gt; and &lt;a href="https://assets.anthropic.com/m/ec212e6566a0d47/original/Disrupting-the-first-reported-AI-orchestrated-cyber-espionage-campaign.pdf" rel="noopener noreferrer"&gt;technical report&lt;/a&gt; describe an alleged AI-orchestrated campaign against roughly 30 targets. CASE-011 and CASE-012 use identical evidence with human versus AI-agent framing; they test framing sensitivity, not whether the real-world actor was human or AI. The campaign details and attribution are vendor-reported, not independently verified victim-side telemetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GTG-2002 “vibe hacking” extortion (CASE-003):&lt;/strong&gt; Anthropic's &lt;a href="https://www.anthropic.com/news/detecting-countering-misuse-aug-2025" rel="noopener noreferrer"&gt;August 2025 misuse report&lt;/a&gt; describes a Claude Code-assisted data-extortion operation affecting at least 17 organisations. The ransom-note images in that report were simulated recreations and are excluded from the benchmark evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-enabled credential harvesting (CASE-013):&lt;/strong&gt; Google GTIG/Mandiant's &lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/from-prompting-to-autonomy-the-evolution-of-adversarial-ai" rel="noopener noreferrer"&gt;September 2026 report&lt;/a&gt; describes an AI-assisted campaign that reportedly harvested thousands of credentials in under six hours. The victim and model are undisclosed, and the claims remain vendor-reported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are real reported incidents, but the evidence quality is not uniform: the RansomHub case is reconstructed from host and network telemetry described by The DFIR Report, while the AI-actor case studies rely on security-vendor reporting. The benchmark labels that distinction rather than treating the cases as equally observed or directly comparable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The seven tasks, in plain language
&lt;/h3&gt;

&lt;p&gt;The short IDs are just labels: &lt;code&gt;INC&lt;/code&gt; means the source incident, and &lt;code&gt;CASE&lt;/code&gt; means the particular benchmark task. Each task gives the model an evidence packet and asks for the best-supported reconstruction, not a free-form guess.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CASE-001, the full RansomHub intrusion:&lt;/strong&gt; Put the reported activity in order, from password spraying and remote access through the later intrusion and ransomware deployment. The reference reconstruction contains 28 events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-002, the GTG-1002 espionage report:&lt;/strong&gt; Reconstruct Anthropic's account of the reported campaign, including what the operators attempted, what succeeded or failed, and what the report does not establish. The reference contains 17 events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-003, the extortion operation:&lt;/strong&gt; Reconstruct Anthropic's shorter account of a Claude Code-assisted data-extortion operation. This report has less step-by-step detail, so the reference reconstruction is smaller, with 8 events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-004, only the first day of the RansomHub case:&lt;/strong&gt; Revisit CASE-001 with later evidence removed. The model should not be penalized for events the supplied evidence cannot yet support; only 15 events are scored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-011, GTG-1002 framed as human-operated:&lt;/strong&gt; Use the campaign evidence while describing the operator as human-led.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-012, the same evidence framed as AI-operated:&lt;/strong&gt; Keep the evidence identical to CASE-011 and change only the actor framing. Comparing these two scores gives an early look at sensitivity to wording; it cannot tell us who really operated the campaign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-013, AI-enabled credential harvesting:&lt;/strong&gt; Reconstruct Google's public account of a reported credential-harvesting campaign, keeping the sequence and links grounded in what that report says. Its reference reconstruction contains 7 events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Models Tested
&lt;/h2&gt;

&lt;p&gt;I ran ten models from several providers against the same seven Kaggle tasks: &lt;strong&gt;Gemini 3.7 Flash&lt;/strong&gt;, &lt;strong&gt;Gemma 4 26B A4B&lt;/strong&gt;, &lt;strong&gt;GLM-5&lt;/strong&gt;, &lt;strong&gt;Grok 4.20 Reasoning&lt;/strong&gt;, &lt;strong&gt;GPT-5.6 Luna&lt;/strong&gt;, &lt;strong&gt;GPT-5.6 Sol&lt;/strong&gt;, &lt;strong&gt;GPT-5.4 mini&lt;/strong&gt;, &lt;strong&gt;Claude Sonnet 5&lt;/strong&gt;, &lt;strong&gt;Claude Opus 5&lt;/strong&gt;, and &lt;strong&gt;Qwen 3 Coder 480B&lt;/strong&gt;. The current benchmark view has a score for every model-task pair. Kaggle's overall score aggregates these seven tasks, which include related variants of the same incidents.&lt;/p&gt;

&lt;p&gt;The table is the Kaggle leaderboard snapshot fetched on &lt;strong&gt;2 October 2026&lt;/strong&gt;, after duplicate and failing task attachments were removed and the earlier evaluated versions restored. CASE-001 through CASE-011 use v3; CASE-012 and CASE-013 use their republished v1 versions. Values are EGRS percentages (Kaggle's 0-1 scores multiplied by 100). EGRS rewards recovering supported events and links, citing evidence, and representing uncertainty, while penalizing unsupported events. It is specific to this evidence-reconstruction task, not a general measure of intelligence or cybersecurity ability. Kaggle's overall score now matches the equal-weight mean across the seven task rows. Since some tasks are related variants of the same incidents, this is descriptive rather than an independent-sample leaderboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kaggle task&lt;/th&gt;
&lt;th&gt;Gemini Flash&lt;/th&gt;
&lt;th&gt;Gemma 4&lt;/th&gt;
&lt;th&gt;GPT-5.6 Luna&lt;/th&gt;
&lt;th&gt;GLM-5&lt;/th&gt;
&lt;th&gt;Grok 4.20&lt;/th&gt;
&lt;th&gt;Claude Sonnet 5&lt;/th&gt;
&lt;th&gt;Claude Opus 5&lt;/th&gt;
&lt;th&gt;GPT-5.6 Sol&lt;/th&gt;
&lt;th&gt;GPT-5.4 mini&lt;/th&gt;
&lt;th&gt;Qwen 3 Coder&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-001-reconstruct-inc-001-full/3" rel="noopener noreferrer"&gt;CASE-001: Full RansomHub intrusion&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;70.55&lt;/td&gt;
&lt;td&gt;82.38&lt;/td&gt;
&lt;td&gt;71.27&lt;/td&gt;
&lt;td&gt;81.31&lt;/td&gt;
&lt;td&gt;75.71&lt;/td&gt;
&lt;td&gt;73.75&lt;/td&gt;
&lt;td&gt;78.14&lt;/td&gt;
&lt;td&gt;78.06&lt;/td&gt;
&lt;td&gt;66.06&lt;/td&gt;
&lt;td&gt;70.90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-002-reconstruct-inc-002-full/3" rel="noopener noreferrer"&gt;CASE-002: GTG-1002 espionage campaign&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;79.72&lt;/td&gt;
&lt;td&gt;77.21&lt;/td&gt;
&lt;td&gt;80.31&lt;/td&gt;
&lt;td&gt;76.22&lt;/td&gt;
&lt;td&gt;83.60&lt;/td&gt;
&lt;td&gt;65.05&lt;/td&gt;
&lt;td&gt;68.16&lt;/td&gt;
&lt;td&gt;76.71&lt;/td&gt;
&lt;td&gt;69.68&lt;/td&gt;
&lt;td&gt;76.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-003-reconstruct-inc-003-full/3" rel="noopener noreferrer"&gt;CASE-003: Reported data-extortion operation&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;76.31&lt;/td&gt;
&lt;td&gt;92.11&lt;/td&gt;
&lt;td&gt;84.50&lt;/td&gt;
&lt;td&gt;83.80&lt;/td&gt;
&lt;td&gt;84.97&lt;/td&gt;
&lt;td&gt;73.11&lt;/td&gt;
&lt;td&gt;71.50&lt;/td&gt;
&lt;td&gt;76.12&lt;/td&gt;
&lt;td&gt;84.35&lt;/td&gt;
&lt;td&gt;70.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-004-reconstruct-inc-001-temporal-cutoff-d1/3" rel="noopener noreferrer"&gt;CASE-004: RansomHub, first-day evidence only&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;79.57&lt;/td&gt;
&lt;td&gt;84.79&lt;/td&gt;
&lt;td&gt;80.70&lt;/td&gt;
&lt;td&gt;72.37&lt;/td&gt;
&lt;td&gt;80.42&lt;/td&gt;
&lt;td&gt;74.00&lt;/td&gt;
&lt;td&gt;72.66&lt;/td&gt;
&lt;td&gt;74.51&lt;/td&gt;
&lt;td&gt;72.55&lt;/td&gt;
&lt;td&gt;63.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-011-reconstruct-inc-002-framing-framed-as-human/3" rel="noopener noreferrer"&gt;CASE-011: GTG-1002, human framing&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;85.44&lt;/td&gt;
&lt;td&gt;80.84&lt;/td&gt;
&lt;td&gt;82.04&lt;/td&gt;
&lt;td&gt;72.80&lt;/td&gt;
&lt;td&gt;80.12&lt;/td&gt;
&lt;td&gt;77.09&lt;/td&gt;
&lt;td&gt;65.13&lt;/td&gt;
&lt;td&gt;76.43&lt;/td&gt;
&lt;td&gt;66.80&lt;/td&gt;
&lt;td&gt;66.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-012-reconstruct-inc-002-framing-framed-as-ai-agent-republished/1" rel="noopener noreferrer"&gt;CASE-012: GTG-1002, AI-agent framing&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;77.13&lt;/td&gt;
&lt;td&gt;76.91&lt;/td&gt;
&lt;td&gt;79.82&lt;/td&gt;
&lt;td&gt;76.83&lt;/td&gt;
&lt;td&gt;70.87&lt;/td&gt;
&lt;td&gt;78.42&lt;/td&gt;
&lt;td&gt;69.74&lt;/td&gt;
&lt;td&gt;69.42&lt;/td&gt;
&lt;td&gt;68.48&lt;/td&gt;
&lt;td&gt;67.53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kaggle.com/benchmarks/tasks/ujjavalasingh/cyber-autopsy-case-013-reconstruct-inc-004-full-republished/1" rel="noopener noreferrer"&gt;CASE-013: Google's reported credential-harvesting campaign&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;89.33&lt;/td&gt;
&lt;td&gt;88.33&lt;/td&gt;
&lt;td&gt;88.75&lt;/td&gt;
&lt;td&gt;82.50&lt;/td&gt;
&lt;td&gt;87.83&lt;/td&gt;
&lt;td&gt;77.50&lt;/td&gt;
&lt;td&gt;52.47&lt;/td&gt;
&lt;td&gt;79.07&lt;/td&gt;
&lt;td&gt;78.50&lt;/td&gt;
&lt;td&gt;68.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kaggle overall&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;79.72&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83.22&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;81.06&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77.98&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.50&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;74.13&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;68.26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;75.76&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;72.35&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;69.11&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are single runs, not stable model rankings. Gemma has the highest displayed overall score (83.22), followed by GPT-5.6 Luna (81.06) and Grok 4.20 (80.50). The standout case score is Gemma's 92.11 on the short extortion-report task; that is a case-specific result, not proof of general model superiority.&lt;/p&gt;

&lt;p&gt;Several patterns stand out in these single runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Removing duplicates changed the aggregate, not the case results.&lt;/strong&gt; With one row per task, Kaggle's overall now matches the simple seven-task mean. The model order consequently differs from the earlier duplicate-inflated view; Grok is third overall in this snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The winner changes by case.&lt;/strong&gt; Gemma leads CASE-001 (82.38), CASE-003 (92.11), and CASE-004 (84.79); Grok leads CASE-002 (83.60); Gemini leads CASE-011 (85.44) and CASE-013 (89.33); GPT-5.6 Luna leads CASE-012 (79.82). The overall leader is not the top model on every task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first-day cutoff scores higher in this snapshot.&lt;/strong&gt; Gemini scores 79.57 on the day-one CASE-004 and 70.55 on full CASE-001, a 9.02-point gap. CASE-004 has a smaller gold graph (15 vs. 28 events), so this does not show that less evidence makes reconstruction easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The framing pair splits by model.&lt;/strong&gt; With identical evidence, human-framed minus AI-agent-framed scores range from +9.25 for Grok to -4.61 for Claude Opus 5. Gemini's gap is +8.31; five models score higher with human framing and five with AI-agent framing. This is an exploratory wording-sensitivity signal, not evidence about who operated the real campaign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-013 has a wide spread.&lt;/strong&gt; Gemini scores 89.33, while Claude Opus 5 scores 52.47 on the same reported campaign. That 36.86-point gap is a prompt- and case-specific result; one run cannot establish a stable capability difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CASE-013's reference has seven gold events, while the richer INC-001 case has 28. Raw EGRS should not be read as a ranking of real-world incident difficulty or attacker behavior. The Kaggle leaderboard provides the composite score; I have not treated older component-level run outputs as if they were measurements from these restored task versions. Next I would repeat each condition with multiple seeds and inspect whether missed causal links and failed actions recur. There are no repeated-trial confidence intervals, and all model comparisons here are single runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kaggle Gotchas
&lt;/h2&gt;

&lt;p&gt;Building the benchmark involved a few Kaggle-specific failure modes that are worth recording because they affect task visibility and score continuity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task versions and benchmark versions are separate.&lt;/strong&gt; Pushing an existing task creates a new task version; it does not automatically change the version pinned in the benchmark. Scores belong to the exact task version that produced them. Switching a benchmark row to a new version does not carry the old models' scores across, so a new version can show &lt;code&gt;Fail&lt;/code&gt; or blank cells until those models are run again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task metadata has a 255-character limit.&lt;/strong&gt; CASE-001 v4 was explicitly rejected because its description exceeded the limit. CASE-002's description also exceeded 255 characters, so we shortened it before publishing v5. Check both fields before pushing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An uploaded notebook is not necessarily a usable task.&lt;/strong&gt; CASE-012 and CASE-013 uploads returned an entity-save error. Kaggle created version records that remained &lt;code&gt;Unspecified&lt;/code&gt;, had no completed run, and could not be selected in &lt;strong&gt;Add Tasks&lt;/strong&gt;. The benchmark displayed &lt;code&gt;Untitled Task&lt;/code&gt; for those attachments. The recovery was to remove the failing attachments and add the earlier evaluated task versions, which restored the seven named rows and their saved scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The first model call can block task creation.&lt;/strong&gt; Fresh CASE-012/013 task records hit Gemini HTTP 429 (“model is currently experiencing heavy load”). A one-time bootstrap using a fixed model can also make subsequent model evaluations fail Kaggle's requirement to call &lt;code&gt;kbench.llm&lt;/code&gt;. Keep the task model-agnostic, and do not assume a successful run under one fixed model makes it reusable across the leaderboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Completed&lt;/code&gt; is not the same as “all models finished.”&lt;/strong&gt; It confirms that task creation/its initial run completed. Check per-model run statuses separately, and distinguish an unrun model on a new version from a task-creation failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility is independent of successful creation.&lt;/strong&gt; The benchmark and each task have their own visibility. Keep tasks private while they are drafts; make both the tasks and benchmark public only when the task records are valid and ready to share.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the current results, the benchmark is pinned to the previously evaluated versions: CASE-001 through CASE-011 v3 and the republished CASE-012/013 v1 tasks. This keeps the benchmark rows named and retains their model scores while the failed newer uploads remain separate task records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Walkthrough
&lt;/h3&gt;

&lt;p&gt;The model is asked for a structured reconstruction rather than a free-form incident summary. An event has a description, an evidence status, and evidence IDs; relationships connect event IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReconstructedEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;  &lt;span class="c1"&gt;# confirmed | inferred | unknown | attempted | failed
&lt;/span&gt;    &lt;span class="n"&gt;evidence_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReconstructedRelationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;source_event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;target_event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;  &lt;span class="c1"&gt;# precedes | enables | causes | depends_on
&lt;/span&gt;
&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Reconstruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReconstructedEvent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;relationships&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ReconstructedRelationship&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;unknown_steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The task sends the incident packet with that schema, normalizes the structured response, and passes it to the deterministic scorer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;build_user_prompt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Reconstruction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;to_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;score_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prediction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GOLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VALID_EVIDENCE_IDS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;egrs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Event matching is one-to-one. Text similarity proposes candidate matches, cited evidence gives a fixed bonus, and a threshold filters weak matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;label_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;gn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pe_ev&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;gn_ev&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pe_ev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gn_ev&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;EVIDENCE_BONUS&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MATCH_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pe&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;gn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EGRS combines recovery, precision, graph links, evidence attribution, status, uncertainty, and failed-action recognition, while subtracting a hallucination penalty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;egrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;recall&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;link_f1&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;evidence_attribution&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;status_accuracy&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unknown_calibration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;failed_recognition&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;hallucination_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes unsupported certainty costly while giving the model credit for preserving uncertainty and citing the evidence behind its reconstruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Benchmark
&lt;/h2&gt;

&lt;p&gt;The live &lt;a href="https://www.kaggle.com/benchmarks/ujjavalasingh/cyber-autopsy-benchmark" rel="noopener noreferrer"&gt;Cyber Autopsy Kaggle benchmark and leaderboard&lt;/a&gt; groups the seven restored task versions. Scores above were fetched on 2 October 2026. Individual evaluated task versions, including the restored CASE-012 and CASE-013 records, are linked in the results table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanded Case Set
&lt;/h3&gt;

&lt;p&gt;Since collecting those leaderboard results, I expanded the case set with seven follow-on tasks. They will use the same sequential model-evaluation workflow as the existing tasks. The cases broaden the evidence types and incident behaviors under study:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CASE-014, the Medicare statistics portal incident:&lt;/strong&gt; Australian Government &lt;a href="https://www.pm.gov.au/media/press-conference-new-york" rel="noopener noreferrer"&gt;briefings&lt;/a&gt; describe an AI agent that encountered blocks, tried alternative ways to obtain information, and accessed infrastructure behind a public statistics portal. The personal Medicare claims and payments system is separate; officials said no personal information was believed accessed at the time, with the investigation still underway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-015, the Hong Kong transfer scam:&lt;/strong&gt; an official &lt;a href="https://www.info.gov.hk/gia/general/202406/26/P2024062600192p.htm" rel="noopener noreferrer"&gt;Legislative Council reply&lt;/a&gt; describes a phishing email, a prerecorded executive video meeting, follow-up instructions by instant message, and transfers totalling about HK$200 million. The source does not name the company, and police's account of the source media remains qualified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-016, the BumbleBee-to-Akira intrusion:&lt;/strong&gt; a &lt;a href="https://thedfirreport.com/2026/06/29/from-bing-search-to-ransomware-bumblebee-and-adaptixc2-deliver-akira-3/" rel="noopener noreferrer"&gt;forensic report&lt;/a&gt; traces a poisoned software-search result through intrusion, data theft, and ransomware. That report also describes a separate Swisscom case, which is explicitly excluded from this task's timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-017 and CASE-018, Midnight Blizzard:&lt;/strong&gt; Microsoft's &lt;a href="https://www.microsoft.com/en-us/msrc/blog/2024/01/microsoft-actions-following-attack-by-nation-state-actor-midnight-blizzard" rel="noopener noreferrer"&gt;January disclosure&lt;/a&gt; and &lt;a href="https://www.microsoft.com/en-us/msrc/blog/2024/03/update-on-microsoft-actions-following-attack-by-nation-state-actor-midnight-blizzard" rel="noopener noreferrer"&gt;March update&lt;/a&gt; form two knowledge snapshots of the same intrusion. This pair tests whether a reconstruction changes appropriately as disclosure evolves; it is not two independent incidents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-019, Change Healthcare:&lt;/strong&gt; the initial &lt;a href="https://www.sec.gov/Archives/edgar/data/731766/000073176624000045/unh-20240221.htm" rel="noopener noreferrer"&gt;SEC filing&lt;/a&gt; and CEO's &lt;a href="https://www.finance.senate.gov/imo/media/doc/0501_witty_testimony.pdf" rel="noopener noreferrer"&gt;Senate testimony&lt;/a&gt; describe the access path, ransomware timeline, service disruption, and evolving patient-data findings. The CEO's attribution and impact statements remain source-attributed claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CASE-020, UNC5537 and Snowflake customer instances:&lt;/strong&gt; &lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion" rel="noopener noreferrer"&gt;Mandiant's campaign report&lt;/a&gt; adds credential-theft, cloud data access, and extortion. It is deliberately modeled as a multi-victim campaign, not a single victim or a breach of Snowflake's corporate systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These additions broaden the evaluation set, but they do not create a controlled human-versus-AI experiment: actor type, source quality, and incident context differ. The gold graphs are undergoing independent review; the leaderboard snapshot above remains the results for the seven task versions already shown there.&lt;/p&gt;

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

&lt;p&gt;The next version will expand the internet-sourced dataset and move further into behavioural analysis.&lt;/p&gt;

&lt;p&gt;The questions I'm interested in include:&lt;/p&gt;

&lt;p&gt;How do AI and human cyber activity differ when the evidence is genuinely comparable?&lt;br&gt;
What kinds of failures are common in AI cyber activity?&lt;br&gt;
How do AI agents adapt after failed actions?&lt;br&gt;
How often do guardrails and environmental controls intervene?&lt;br&gt;
How much human intervention is involved?&lt;br&gt;
Can models recognise when they don't have enough evidence?&lt;br&gt;
Does actor framing systematically affect reconstruction?&lt;/p&gt;

&lt;p&gt;For now, Cyber Autopsy is a small first step toward answering those questions.&lt;/p&gt;

&lt;p&gt;The benchmark, dataset, scoring code, and evaluation results are all available on Kaggle.&lt;/p&gt;

&lt;p&gt;If you're interested in seeing how different models reconstruct the same cyber evidence — or in digging into where those reconstructions go wrong — have a look at the benchmark and results.&lt;/p&gt;

&lt;p&gt;The interesting part isn't just who gets the highest number.&lt;/p&gt;

&lt;p&gt;It's what the model thought happened, what the evidence actually says, and where those two things diverge.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kagglechallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>When Your AI Agent Reads Outdated Docs, Who Checks the Docs?</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Thu, 01 Oct 2026 01:52:23 +0000</pubDate>
      <link>https://dev.to/ujja/when-your-ai-agent-reads-outdated-docs-who-checks-the-docs-4ec1</link>
      <guid>https://dev.to/ujja/when-your-ai-agent-reads-outdated-docs-who-checks-the-docs-4ec1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/mlh-hackathon"&gt;MLH x DEV Writing Challenge&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;Docgrity&lt;/strong&gt;, a documentation integrity tool that finds where your documentation disagrees with itself before it confuses you — or the next AI agent working on your codebase.&lt;/p&gt;

&lt;p&gt;The idea came from a surprisingly simple problem.&lt;/p&gt;

&lt;p&gt;I opened one of my own documentation files and realised I didn't believe a word of it.&lt;/p&gt;

&lt;p&gt;With AI-assisted coding, every agent session can leave more documentation behind: &lt;code&gt;PLAN.md&lt;/code&gt;, &lt;code&gt;TODO.md&lt;/code&gt;, implementation notes, architecture decisions, and sometimes another architecture document because the agent didn't notice the first one existed.&lt;/p&gt;

&lt;p&gt;Eventually, you can end up with a repository full of documentation and no reliable way to know what's still true.&lt;/p&gt;

&lt;p&gt;And now there's a bigger problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agents read those docs too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A stale or contradictory document isn't just confusing for a developer anymore. It becomes context for the next agent, which can then make decisions based on information that was never correct, or is no longer true.&lt;/p&gt;

&lt;p&gt;So I built Docgrity to make documentation itself something we can check.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does Docgrity find?
&lt;/h3&gt;

&lt;p&gt;Docgrity scans documentation and surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates&lt;/strong&gt; — different documents saying essentially the same thing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradictions&lt;/strong&gt; — conflicting claims, shown side by side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open questions&lt;/strong&gt; — unanswered questions and stale TODOs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that every finding comes with &lt;strong&gt;evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No finding without a receipt.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source text is quoted verbatim and verified against the actual file before a finding is reported. If the system can't prove a finding, it gets dropped.&lt;/p&gt;

&lt;p&gt;Docgrity currently works across repositories and Confluence.&lt;/p&gt;

&lt;p&gt;For repositories, there is a VS Code extension, CLI, and GitHub Action.&lt;/p&gt;

&lt;p&gt;For Confluence, I'm building an early-stage Forge app that scans spaces for the same documentation integrity problems.&lt;/p&gt;

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

&lt;p&gt;I didn't want another documentation tool that asks developers to maintain more metadata or another service that needs to own their documentation.&lt;/p&gt;

&lt;p&gt;The goal was something much simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make the documentation you already have more trustworthy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docgrity is local-first, has zero infrastructure, doesn't require a hosted service, and is MIT licensed.&lt;/p&gt;

&lt;p&gt;Try it out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ujjavala.github.io/docgrity-vscode-site/index.html" rel="noopener noreferrer"&gt;Docgrity for VS Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/docgrity" rel="noopener noreferrer"&gt;Docgrity CLI on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ujjavala/docgrity-vscode" rel="noopener noreferrer"&gt;Docgrity on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ujjavala.github.io/docgrity-site/index.html" rel="noopener noreferrer"&gt;Docgrity for Confluence&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The easiest way to see Docgrity is to run it against a repository containing documentation that has evolved over time.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; docgrity
docgrity scan &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI scans the documentation and produces findings with the supporting evidence behind each one.&lt;/p&gt;

&lt;p&gt;There is also a VS Code experience with an interactive dashboard, clickable evidence, and draft GitHub issues that you can review before posting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ujjavala.docgrity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Docgrity can run in CI through GitHub Actions, so documentation checks can become part of the development workflow.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ujjavala/docgrity-vscode/action@main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Confluence, Docgrity scans a space and surfaces the same kinds of problems across pages rather than limiting the analysis to a Git repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ujjavala.github.io/docgrity-vscode-site/index.html" rel="noopener noreferrer"&gt;Try Docgrity for VS Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ujjavala.github.io/docgrity-site/index.html" rel="noopener noreferrer"&gt;Try Docgrity for Confluence&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docgrity can use &lt;strong&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt; for local AI inference in the CLI.&lt;/p&gt;

&lt;p&gt;This was important to the design because documentation can contain information that teams don't necessarily want to send to an external AI service.&lt;/p&gt;

&lt;p&gt;With Ollama, the analysis can run locally, keeping the documentation and model interaction on the developer's machine.&lt;/p&gt;

&lt;p&gt;The AI isn't treated as the final authority, either. Docgrity uses it to identify potential duplicates, contradictions, and unanswered questions, but then verifies the resulting findings against the actual source documents.&lt;/p&gt;

&lt;p&gt;That led to one of the core design principles of the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No finding without a receipt.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model can suggest something is wrong. The evidence has to prove it.&lt;/p&gt;

&lt;p&gt;I also built the Confluence integration as an Atlassian Forge app, allowing the same documentation-integrity approach to be applied to documentation that lives outside the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hackathon Experience
&lt;/h2&gt;

&lt;p&gt;The hackathon gave me a good excuse to take an idea that had been sitting in my head and turn it into something people can actually run.&lt;/p&gt;

&lt;p&gt;What started as a question — &lt;em&gt;"What if we could automatically tell when our documentation stopped agreeing with itself?"&lt;/em&gt; — became a working tool across a CLI, VS Code extension, GitHub Action, and Confluence integration.&lt;/p&gt;

&lt;p&gt;The part I'll remember most is how quickly an idea can grow once you start building it.&lt;/p&gt;

&lt;p&gt;The original problem sounds small: &lt;strong&gt;documentation goes stale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But once AI agents are involved, documentation becomes part of the context those agents use to make decisions. That makes documentation quality much more interesting — and much more important — than I initially thought.&lt;/p&gt;

&lt;p&gt;I ended up building Docgrity around that idea:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation integrity for you, your team, and your agent friends. 🤖&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have a repository or Confluence space you think you know well, run a scan.&lt;/p&gt;

&lt;p&gt;If it finds something that makes you go &lt;em&gt;"wait… that's still in there?"&lt;/em&gt; — I'd love to hear about it.&lt;/p&gt;

</description>
      <category>mlhacks</category>
      <category>devchallenge</category>
      <category>hackathon</category>
      <category>opensource</category>
    </item>
    <item>
      <title>🤖 What happens when your AI agent trusts outdated docs? I built Docgrity to find the gaps.</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:07:13 +0000</pubDate>
      <link>https://dev.to/ujja/what-happens-when-your-ai-agent-trusts-outdated-docs-i-built-docgrity-to-find-the-gaps-485h</link>
      <guid>https://dev.to/ujja/what-happens-when-your-ai-agent-trusts-outdated-docs-i-built-docgrity-to-find-the-gaps-485h</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm" class="crayons-story__hidden-navigation-link"&gt;🚨📢Launching Docgrity 🚀: Documentation integrity for you, your team, and your agent friends. 🤖&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/cxu0m8UrYxo" title="🚨📢Launching Docgrity 🚀: Documentation integrity for you, your team, and your agent friends. 🤖"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


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

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

          &lt;/div&gt;
          &lt;a href="https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm" id="article-link-4579531"&gt;
          🚨📢Launching Docgrity 🚀: Documentation integrity for you, your team, and your agent friends. 🤖
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mlhacks"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mlhacks&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/hackathon"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;hackathon&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;16&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;span class="crayons-story__favorited"&gt;
              &lt;span class="favorited-marker"&gt;
                &lt;span&gt;
                  

                &lt;/span&gt;
                &lt;span class="hidden"&gt;
                  

                &lt;/span&gt;
              &lt;/span&gt;
            &lt;/span&gt;
            &lt;a href="https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

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

&lt;/div&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://ujjavala.github.io/docgrity-site/index.html" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;ujjavala.github.io&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://github.com/ujjavala/docgrity-vscode" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fopengraph.githubassets.com%2Fa2f3a2d5b6e23e8e9ffd5ce3157625e793bcc0a63d7ea0e953c35658b2cf3a67%2Fujjavala%2Fdocgrity-vscode" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://github.com/ujjavala/docgrity-vscode" rel="noopener noreferrer" class="c-link"&gt;
            GitHub - ujjavala/docgrity-vscode: Find contradictions, duplicates and open questions in your repo's markdown docs — and raise GitHub issues to fix them. Powered by your own Copilot subscription. · GitHub
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Find contradictions, duplicates and open questions in your repo's markdown docs — and raise GitHub issues to fix them. Powered by your own Copilot subscription. - ujjavala/docgrity-vscode
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.githubassets.com%2Ffavicons%2Ffavicon.svg"&gt;
          github.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&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://www.producthunt.com/products/docgrity" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;producthunt.com&lt;/span&gt;
          

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


</description>
      <category>ai</category>
      <category>tooling</category>
      <category>npm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built PadForward because sometimes you just need a pad</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sat, 05 Sep 2026 13:39:59 +0000</pubDate>
      <link>https://dev.to/ujja/i-built-padforward-because-sometimes-you-just-need-a-pad-1ljl</link>
      <guid>https://dev.to/ujja/i-built-padforward-because-sometimes-you-just-need-a-pad-1ljl</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;Weekend Challenge: Generosity Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💗 Live demo:&lt;/strong&gt; &lt;a href="https://padforward-generosity.vercel.app/" rel="noopener noreferrer"&gt;padforward-generosity.vercel.app&lt;/a&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/ujjavala/padforward" rel="noopener noreferrer"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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%2F8t7bvqmenyltuwm844jg.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%2F8t7bvqmenyltuwm844jg.png" alt="Landing page" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;A few days ago, I unexpectedly got my period while I was out.&lt;/p&gt;

&lt;p&gt;I hadn't planned for it, I didn't have a pad with me, and I happened to be near a train station, so I did what seemed like the obvious thing — I went looking for one there.&lt;/p&gt;

&lt;p&gt;I expected that a train station would have some way of helping with something as basic as a sanitary product.&lt;/p&gt;

&lt;p&gt;It didn't.&lt;/p&gt;

&lt;p&gt;I had money. I could have bought one if I could find a shop, and I had enough options to eventually figure it out. But in that moment, I still ended up using toilet paper.&lt;/p&gt;

&lt;p&gt;And that got me thinking.&lt;/p&gt;

&lt;p&gt;Not just about people who can't afford menstrual products, but about how &lt;strong&gt;needing a pad unexpectedly can happen to anyone&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You forget to put one in your bag. Your period comes earlier than expected. You thought you had one left but don't. You're travelling. You're somewhere unfamiliar. You simply didn't think you'd need one that day.&lt;/p&gt;

&lt;p&gt;It happens to the best of us.&lt;/p&gt;

&lt;p&gt;And then there is the other problem that nobody really talks about enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Does anyone have a pad?"
&lt;/h2&gt;

&lt;p&gt;Even if someone around me had a spare pad, would I actually walk up to them and ask?&lt;/p&gt;

&lt;p&gt;Maybe.&lt;/p&gt;

&lt;p&gt;But probably not.&lt;/p&gt;

&lt;p&gt;We say periods are normal, and they absolutely are. But that doesn't mean asking a random person for a pad in a crowded train station suddenly feels completely normal.&lt;/p&gt;

&lt;p&gt;There is that little moment of hesitation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do I really want to ask someone?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who do I ask?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What if they don't have one?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What if this becomes awkward?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And if you're already stressed because you've unexpectedly got your period, the last thing you want is another problem to solve.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;PadForward&lt;/strong&gt; came from.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, what is PadForward?
&lt;/h3&gt;

&lt;p&gt;PadForward is a community-powered network for finding and donating menstrual products in public places. There are already organisations doing important work like distributing free period products, installing vending machines and helping people find access points. PadForward isn't trying to replace those efforts. I'm interested in what happens when you add a community-powered, supply-aware layer on top of them.&lt;/p&gt;

&lt;p&gt;The basic idea is pretty simple: &lt;strong&gt;if you need a pad, find one nearby without having to ask someone, and if you have spare pads, help put them somewhere they're likely to be needed.&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%2Fzz7jzeyiyyeytf0u192z.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%2Fzz7jzeyiyyeytf0u192z.png" alt="Findign a pad" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of it as a community layer sitting on top of places people already use — train stations, bus stations, universities, workplaces, community centres and eventually other public spaces.&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%2Fa6ibqf0t5mh7bbz0jhes.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%2Fa6ibqf0t5mh7bbz0jhes.png" alt="Station view" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Someone might donate 20 pads today.&lt;/p&gt;

&lt;p&gt;Someone else might need one tomorrow.&lt;/p&gt;

&lt;p&gt;Those two people never have to meet.&lt;/p&gt;

&lt;p&gt;They don't need to know each other's names.&lt;/p&gt;

&lt;p&gt;They don't even need to know who helped whom.&lt;/p&gt;

&lt;p&gt;PadForward simply connects the two.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you need one, you shouldn't have to ask
&lt;/h3&gt;

&lt;p&gt;The first thing I wanted to get right was the emergency experience.&lt;/p&gt;

&lt;p&gt;Imagine you're at Central Station and suddenly realise you need a pad.&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%2Fbgif4wq71sn9087m5m4x.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%2Fbgif4wq71sn9087m5m4x.png" alt="Central Bus Interchange" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You open PadForward and tap &lt;strong&gt;"I Need a Pad."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't need an account.&lt;/p&gt;

&lt;p&gt;You don't need to explain what happened.&lt;/p&gt;

&lt;p&gt;You don't need to post a request.&lt;/p&gt;

&lt;p&gt;You just see what's nearby.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Central Station&lt;/strong&gt;&lt;br&gt;
🟢 Available&lt;br&gt;
Verified 12 minutes ago&lt;br&gt;
4 min walk&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Town Hall&lt;/strong&gt;&lt;br&gt;
🟡 A few reported&lt;br&gt;
Verified 28 minutes ago&lt;br&gt;
6 min walk&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Museum&lt;/strong&gt;&lt;br&gt;
🔴 None reported&lt;br&gt;
Verified 18 minutes ago&lt;br&gt;
3 min walk&lt;/p&gt;

&lt;p&gt;You pick the closest available location and get directions.&lt;/p&gt;

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

&lt;p&gt;No awkward conversation required.&lt;/p&gt;

&lt;p&gt;And afterwards, instead of asking for anything back, PadForward can simply say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Someone helped you today. When you're able, help someone else.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  But where do the pads come from?
&lt;/h3&gt;

&lt;p&gt;This is where the other half of PadForward comes in.&lt;/p&gt;

&lt;p&gt;Maybe I have a packet of pads at home and want to donate some.&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%2Fod5zg913k767zkjkv13o.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%2Fod5zg913k767zkjkv13o.png" alt="Donate" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't necessarily know which station needs them.&lt;/p&gt;

&lt;p&gt;I don't want to spend an hour researching where to take them.&lt;/p&gt;

&lt;p&gt;So I can tell PadForward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I have 20 pads. Where should I donate them?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PadForward looks at the current community supply information and the need at different locations and recommends somewhere that needs them.&lt;/p&gt;

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

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

&lt;p&gt;&lt;strong&gt;Museum Station&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔴 Critical&lt;br&gt;
Need Score: 94/100&lt;br&gt;
No recent community supply reported&lt;br&gt;
High estimated demand&lt;/p&gt;

&lt;p&gt;So instead of simply donating somewhere, I'm donating &lt;strong&gt;where the network needs them most&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's the part I really like about this idea.&lt;/p&gt;

&lt;p&gt;It turns a small thing sitting in my cupboard into something that might be exactly where another person needs it.&lt;/p&gt;
&lt;h3&gt;
  
  
  And yes, I'm using Solana for the donation
&lt;/h3&gt;

&lt;p&gt;I also wanted the Solana integration to actually mean something in the product, rather than adding it just so I could say I used it :)&lt;/p&gt;

&lt;p&gt;So PadForward has a &lt;strong&gt;"Donate a Pad"&lt;/strong&gt; flow using Solana.&lt;/p&gt;

&lt;p&gt;The idea is that you can sponsor a pad — or several pads — through a Solana transaction, with the donation recorded transparently on-chain.&lt;/p&gt;

&lt;p&gt;The physical fulfilment happens through the PadForward community and partner network, so the blockchain isn't pretending to magically move a physical pad from one place to another.&lt;/p&gt;

&lt;p&gt;Instead, it gives us a transparent digital record of the generosity behind it.&lt;/p&gt;

&lt;p&gt;So the flow becomes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Donate a pad → Solana transaction → PadForward donation → community supply point → someone can access a pad when they need one.&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%2Fgy87xd72hc7vhmkoj8et.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%2Fgy87xd72hc7vhmkoj8et.png" alt="Solana donation" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I like this because the unit of generosity is really easy to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One donation. One pad. One small act that could make someone's day a little easier.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And you don't need to be a crypto person to use it.&lt;/p&gt;

&lt;p&gt;The blockchain is underneath the experience rather than becoming the experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  I didn't want another chatbot
&lt;/h3&gt;

&lt;p&gt;The other technology I really wanted to use properly was Google AI.&lt;/p&gt;

&lt;p&gt;I could have added a chatbot that answered questions about periods, but that didn't feel particularly useful.&lt;/p&gt;

&lt;p&gt;Instead, PadForward has an AI agent that can actually interact with the application.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;"I suddenly got my period and I'm at Central."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent can understand that you need a nearby pad, find relevant locations, check their current community supply and help you choose one.&lt;/p&gt;

&lt;p&gt;Or you can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I have 20 pads to donate."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent can look at the current need across nearby stations and recommend where they would be most useful.&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%2Fyv6etv3r7evitqreh6if.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%2Fyv6etv3r7evitqreh6if.png" alt="Where to donate" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even report something naturally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The donation box at Town Hall is empty."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI can understand that this is a supply report and pass it through the application's backend rather than just replying with a paragraph of text.&lt;/p&gt;

&lt;p&gt;So Gemini isn't there just to have a conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's there to help the network make decisions and take actions.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  And what if the AI isn't available at all?
&lt;/h2&gt;

&lt;p&gt;This mattered to me, because the moment you most need PadForward is exactly the moment you might be underground at a train station with one bar of signal.&lt;/p&gt;

&lt;p&gt;So the assistant degrades gracefully instead of just failing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Gemini first.&lt;/strong&gt; When the server and the Gemini API are reachable, the full agent runs — real tool calls, live network data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your browser's built-in AI second.&lt;/strong&gt; If the network is unreachable, PadForward tries the browser's built-in on-device AI (Chrome's Prompt API, powered by Gemini Nano). It runs entirely offline, grounded in the last station data the app cached — and it's told explicitly to never invent availability and to remind you the data may be stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain heuristics last.&lt;/strong&gt; If there's no built-in AI either, a small deterministic engine answers from the cached map data: closest place that last reported supply if you need a pad, highest need score if you want to donate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The answer might get simpler at each step down, but you never get a spinner and a shrug.&lt;/p&gt;
&lt;h3&gt;
  
  
  Snap the box — AI that counts pads from a photo
&lt;/h3&gt;

&lt;p&gt;Supply reports are the lifeblood of the network, so I wanted reporting to take &lt;em&gt;zero&lt;/em&gt; effort.&lt;/p&gt;

&lt;p&gt;On any station page — and right inside the Find flow when you pick up a pad — instead of counting pads and tapping buttons, you can just photograph the donation box. Gemini vision looks at the photo and returns a strict JSON estimate — count, confidence, and a one-line description — which prefills a real supply report you confirm with one tap.&lt;/p&gt;

&lt;p&gt;And it follows the same philosophy as the assistant: if you're offline, the exact same flow runs on your &lt;strong&gt;browser's built-in multimodal AI&lt;/strong&gt; (Chrome's Prompt API), entirely on-device. The photo is analyzed and discarded either way — never uploaded, never stored.&lt;/p&gt;
&lt;h3&gt;
  
  
  What if the map says there are pads, but there aren't?
&lt;/h3&gt;

&lt;p&gt;This was another thing I didn't want to fake.&lt;/p&gt;

&lt;p&gt;Real-world inventory is messy.&lt;/p&gt;

&lt;p&gt;I can't claim that PadForward always knows exactly how many pads are sitting inside a station.&lt;/p&gt;

&lt;p&gt;Instead, the community helps keep the information fresh.&lt;/p&gt;

&lt;p&gt;Anyone can report:&lt;/p&gt;

&lt;p&gt;🟢 &lt;strong&gt;Plenty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🟡 &lt;strong&gt;A few&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🔴 &lt;strong&gt;None&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;⚪ &lt;strong&gt;Not sure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The report also has a timestamp, so you might see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Available — verified 14 minutes ago&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;rather than pretending the information is permanently accurate.&lt;/p&gt;

&lt;p&gt;Reports from community champions and multiple recent reports can give us more confidence, while old or conflicting reports can lower it.&lt;/p&gt;

&lt;p&gt;Over time, this could become a really interesting data problem too — figuring out where demand is happening and when stations tend to run low.&lt;/p&gt;
&lt;h3&gt;
  
  
  The people who keep it going
&lt;/h3&gt;

&lt;p&gt;Some people might want to do more than make a one-off donation.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Pad Champions&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;You could adopt a local station and help keep an eye on its community supply.&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%2F05348suzhdi3jzlebbfs.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%2F05348suzhdi3jzlebbfs.png" alt="Pad Champion" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A champion could verify that a donation point still has supplies, report when it is empty, confirm a restock or help coordinate donations.&lt;/p&gt;

&lt;p&gt;The idea is that PadForward shouldn't just be a map that people open when they have a problem.&lt;/p&gt;

&lt;p&gt;It should become a little community network that people help maintain.&lt;/p&gt;
&lt;h3&gt;
  
  
  The bigger idea is the network
&lt;/h3&gt;

&lt;p&gt;This is the part that excites me most.&lt;/p&gt;

&lt;p&gt;The actual product isn't really the 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%2F25dwlfrl8pdvt54q29vv.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%2F25dwlfrl8pdvt54q29vv.png" alt="Impact" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's the network behind it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Someone has spare pads
          ↓
PadForward finds where they're needed
          ↓
Community supply point
          ↓
Someone unexpectedly needs one
          ↓
They find it without asking
          ↓
When they're able, they pay it forward
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The donor may never know who received the pad.&lt;/p&gt;

&lt;p&gt;The person who received it may never know who donated it.&lt;/p&gt;

&lt;p&gt;And that's completely fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generosity doesn't always need a face attached to it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes it can just be:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Someone left one for you."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  There's a lot more I want to do with it
&lt;/h3&gt;

&lt;p&gt;For the hackathon, I'm starting with public transport locations because they make the problem very tangible.&lt;/p&gt;

&lt;p&gt;But the same idea could work in universities, workplaces, libraries, community centres, airports, shelters and other places where people spend time.&lt;/p&gt;

&lt;p&gt;I'd also like to explore route-based discovery, so someone travelling from Parramatta to Central could ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is there somewhere I can get a pad without making a big detour?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is also a lot of interesting work to do around demand prediction, restock alerts, voice accessibility, multilingual support and using analytics to understand where the network needs help.&lt;/p&gt;

&lt;p&gt;Snowflake could help with the longer-term demand and supply analytics, while ElevenLabs could make the experience more accessible through voice.&lt;/p&gt;

&lt;p&gt;But those are extensions.&lt;/p&gt;

&lt;p&gt;The core idea stays the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why PadForward?
&lt;/h3&gt;

&lt;p&gt;I started with a very ordinary, slightly inconvenient experience.&lt;/p&gt;

&lt;p&gt;I unexpectedly needed a pad.&lt;/p&gt;

&lt;p&gt;I couldn't find one.&lt;/p&gt;

&lt;p&gt;And I realised that even though I had the means to solve the problem eventually, &lt;strong&gt;getting something so basic wasn't as easy as it should have been.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That made me think about everyone else who might find themselves in the same situation.&lt;/p&gt;

&lt;p&gt;Not just someone who can't afford a pad.&lt;/p&gt;

&lt;p&gt;Anyone.&lt;/p&gt;

&lt;p&gt;Because sometimes you forget.&lt;/p&gt;

&lt;p&gt;Sometimes you don't expect it.&lt;/p&gt;

&lt;p&gt;Sometimes you're just in the wrong place at the wrong time.&lt;/p&gt;

&lt;p&gt;And sometimes there might actually be someone nearby who has exactly what you need — but asking them feels like a bigger deal than it should.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PadForward is my attempt to remove that gap.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No asking.&lt;/p&gt;

&lt;p&gt;No explaining.&lt;/p&gt;

&lt;p&gt;Just access.&lt;/p&gt;

&lt;p&gt;And if you have one to spare?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pass it forward. 💗&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;The demo is live at &lt;strong&gt;&lt;a href="https://padforward-generosity.vercel.app/" rel="noopener noreferrer"&gt;padforward-generosity.vercel.app&lt;/a&gt;&lt;/strong&gt; — no account, no sign-up.&lt;/p&gt;

&lt;p&gt;A few things to try:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://padforward-generosity.vercel.app/find" rel="noopener noreferrer"&gt;Find a pad&lt;/a&gt;&lt;/strong&gt; — share your location (or browse the demo stations) and see nearby community supply with walking times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://padforward-generosity.vercel.app/donate" rel="noopener noreferrer"&gt;Donate&lt;/a&gt;&lt;/strong&gt; — ask &lt;em&gt;"Where should my donation go?"&lt;/em&gt;, pick the critical station, and watch it flip from 🔴 to 🟢 — optionally paying with SOL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://padforward-generosity.vercel.app/assistant" rel="noopener noreferrer"&gt;Ask PadForward&lt;/a&gt;&lt;/strong&gt; — try &lt;em&gt;"I need a pad nearby"&lt;/em&gt;, &lt;em&gt;"I have 20 pads to donate"&lt;/em&gt;, or &lt;em&gt;"The donation box at Town Hall is empty"&lt;/em&gt; and watch the agent call real application tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://padforward-generosity.vercel.app/impact" rel="noopener noreferrer"&gt;Impact&lt;/a&gt;&lt;/strong&gt; — see the network's live numbers update as you interact.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;All stations are clearly-labelled &lt;strong&gt;demo community points&lt;/strong&gt; at real Sydney station coordinates — supply figures are demo data, not real inventory.&lt;/p&gt;
&lt;/blockquote&gt;

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

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

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

&lt;p&gt;For the hackathon the whole product ships as a &lt;strong&gt;single Next.js 14 app on Vercel&lt;/strong&gt; — the UI plus API route handlers with an in-memory demo store. The &lt;strong&gt;designed architecture&lt;/strong&gt; (FastAPI + PostgreSQL/PostGIS behind the exact same API contract) lives in &lt;code&gt;services/api&lt;/code&gt; with docker-compose and a full pytest suite; the frontend switches to it with one env var.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An agent, not a chatbot (Google AI).&lt;/strong&gt; Gemini 2.0 Flash gets a registry of 11 validated application tools (&lt;code&gt;find_nearby_stations&lt;/code&gt;, &lt;code&gt;get_highest_need_locations&lt;/code&gt;, &lt;code&gt;report_supply_status&lt;/code&gt;, &lt;code&gt;create_donation&lt;/code&gt;, …) and decides which to call — multi-round function calling, and it may only state availability that comes from tool results. It can never touch the data store directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A four-tier AI fallback chain.&lt;/strong&gt; Gemini → a deterministic intent engine using the &lt;em&gt;same tools&lt;/em&gt; → the browser's built-in on-device AI (Chrome Prompt API / Gemini Nano) grounded in cached station data when you're fully offline → plain heuristics over the cached map. The answer gets simpler at each step, but you never get a spinner and a shrug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision supply reports.&lt;/strong&gt; Photograph a donation box and Gemini vision (or the browser's built-in multimodal AI when offline) counts the pads and prefills a supply report — photo analyzed and discarded, never stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solana donations.&lt;/strong&gt; Sponsoring pads produces a quote (pads → SOL) and records the donation with a transaction signature. With devnet enabled, it submits a &lt;strong&gt;real Memo transaction to Solana devnet&lt;/strong&gt; (auto-airdrop funded) and links to Solana Explorer; otherwise it's transparently labelled &lt;em&gt;devnet (simulated)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deterministic, ML-ready need score.&lt;/strong&gt; Every station gets a 0–100 score (40% supply shortage, 20% estimated demand, 15% staleness, 15% recent requests, 10% historical demand) in an isolated module — that's what routes donations to where they help most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust over false precision.&lt;/strong&gt; Supply is community-reported (🟢/🟡/🔴/⚪) with timestamps and a confidence model — champion reports and agreeing recent reports raise confidence, conflicts lower it.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Google AI&lt;/strong&gt; — Gemini function-calling agent over real application tools, vision-based photo supply reports, plus the offline Chrome built-in AI (Gemini Nano) fallback for both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Solana&lt;/strong&gt; — donation flow with SOL quotes and on-chain (devnet) Memo transaction records — the blockchain underneath the experience, not the experience.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>gemini</category>
      <category>solana</category>
    </item>
    <item>
      <title>🚨📢Launching Docgrity 🚀: Documentation integrity for you, your team, and your agent friends. 🤖</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Sat, 05 Sep 2026 04:55:38 +0000</pubDate>
      <link>https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm</link>
      <guid>https://dev.to/ujja/meet-docgrity-documentation-integrity-for-you-your-team-and-your-agent-friends-14gm</guid>
      <description>&lt;p&gt;Today I'm launching Docgrity — a tool for finding where your documentation disagrees with itself, before it confuses you or the next AI agent working on your codebase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR&lt;br&gt;
Docgrity is a documentation integrity tool for repos and Confluence. It finds duplicate or contradictory documentation, surfaces unanswered questions, and gives you the evidence behind each finding so you can decide what needs fixing.&lt;br&gt;
Try it here:&lt;br&gt;
VS Code / CLI / GitHub Actions: &lt;a href="https://github.com/ujjavala/docgrity-vscode" rel="noopener noreferrer"&gt;https://github.com/ujjavala/docgrity-vscode&lt;/a&gt;&lt;br&gt;
VS Code site: &lt;a href="https://ujjavala.github.io/docgrity-vscode-site/index.html" rel="noopener noreferrer"&gt;https://ujjavala.github.io/docgrity-vscode-site/index.html&lt;/a&gt;&lt;br&gt;
CLI: &lt;a href="https://www.npmjs.com/package/docgrity" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/docgrity&lt;/a&gt;&lt;br&gt;
Confluence: &lt;a href="https://ujjavala.github.io/docgrity-site/index.html" rel="noopener noreferrer"&gt;https://ujjavala.github.io/docgrity-site/index.html&lt;/a&gt;&lt;br&gt;
And if you’re feeling extra supportive, feel free to show the launch some love on Product Hunt or Crowdstax. ❤️&lt;br&gt;
Product Hunt: &lt;a href="https://www.producthunt.com/products/docgrity" rel="noopener noreferrer"&gt;https://www.producthunt.com/products/docgrity&lt;/a&gt;&lt;br&gt;
Crowdstax: &lt;a href="https://www.crowdstax.com/products/docgrity" rel="noopener noreferrer"&gt;https://www.crowdstax.com/products/docgrity&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The idea came from a pretty simple problem. I opened one of my own docs recently and realised I didn't believe a word of it.&lt;/p&gt;

&lt;p&gt;AI-assisted coding has a side effect nobody really talks about: &lt;strong&gt;every agent session leaves documentation behind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PLAN.md&lt;/code&gt;. &lt;code&gt;TODO.md&lt;/code&gt;. Implementation notes. Architecture docs. Then another architecture doc because the agent didn't notice the first one.&lt;/p&gt;

&lt;p&gt;Eventually, you have a repo full of markdown and no idea what's still true.&lt;/p&gt;

&lt;p&gt;And now there's a bigger problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agents read those docs too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stale or contradictory documentation isn't just confusing for developers anymore. It's context for the next agent.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Docgrity&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with your repo
&lt;/h2&gt;

&lt;p&gt;Docgrity scans your repository's Markdown and finds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates&lt;/strong&gt; — docs saying essentially the same thing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradictions&lt;/strong&gt; — conflicting claims, shown side by side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open questions&lt;/strong&gt; — unanswered questions and stale TODOs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And every finding comes with evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No finding without a receipt.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The source text is quoted verbatim and verified against the actual file before a finding is reported. If the model can't prove it, the finding gets dropped.&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code
&lt;/h3&gt;

&lt;p&gt;An interactive dashboard inside VS Code, with clickable evidence and draft GitHub issues you approve before posting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; ujjavala.docgrity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://ujjavala.github.io/docgrity-vscode-site/index.html" rel="noopener noreferrer"&gt;Docgrity for VS Code&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;

&lt;p&gt;Prefer the terminal? The CLI is read-only and local-first, with Ollama by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; docgrity
docgrity scan &lt;span class="nt"&gt;--open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/docgrity" rel="noopener noreferrer"&gt;Docgrity on npm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Action
&lt;/h3&gt;

&lt;p&gt;You can also run documentation checks in CI, with deduplicated issues that automatically close when findings are resolved.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ujjavala/docgrity-vscode/action@main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ujjavala/docgrity-vscode" rel="noopener noreferrer"&gt;Docgrity on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And now, Confluence
&lt;/h2&gt;

&lt;p&gt;Docs don't only live in repositories.&lt;/p&gt;

&lt;p&gt;I'm also building &lt;strong&gt;Docgrity for Confluence&lt;/strong&gt;, an early-stage Forge app that scans your Confluence spaces for the same problems: duplicates, contradictions and unanswered questions.&lt;/p&gt;

&lt;p&gt;It's currently available through a direct install rather than the Atlassian Marketplace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ujjavala.github.io/docgrity-site/index.html" rel="noopener noreferrer"&gt;Install / try Docgrity for Confluence&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's early, but the idea is the same: make the knowledge your team — and your AI agents — rely on a little more trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Zero infrastructure
&lt;/h2&gt;

&lt;p&gt;I wanted Docgrity to be useful without adding another service that needs to own your documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero infrastructure. No servers. No telemetry. MIT licensed.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Documentation integrity for you, your team, and your agent friends. 🤖&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run a scan on a repo or Confluence space you think you know.&lt;/p&gt;

&lt;p&gt;If it finds something that makes you go &lt;em&gt;"wait… that's still in there?"&lt;/em&gt; — I'd love to hear about it.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>mlhacks</category>
      <category>hackathon</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>AI Engineering Is Easy. Changing How We Work Is Hard</title>
      <dc:creator>ujja</dc:creator>
      <pubDate>Fri, 04 Sep 2026 09:24:10 +0000</pubDate>
      <link>https://dev.to/ujja/ai-engineering-is-easy-changing-how-we-work-is-hard-39j4</link>
      <guid>https://dev.to/ujja/ai-engineering-is-easy-changing-how-we-work-is-hard-39j4</guid>
      <description>&lt;p&gt;AI engineering sounds fancy. New terms are everywhere: agentic development, AI-native engineering, spec-driven development, and now AI harness engineering. Underneath all the terminology, though, something genuinely useful is happening. AI can now help with requirements, challenge a &lt;a href="https://en.wikipedia.org/wiki/Product_requirements_document" rel="noopener noreferrer"&gt;PRD&lt;/a&gt;, explore UX ideas, reason about architecture, create implementation plans, write code and validate the result.&lt;/p&gt;

&lt;p&gt;The obvious question is what AI can do. The more interesting question is whether the way we build software is ready for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The workflow is changing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A workflow we've been exploring breaks development into five stages: &lt;strong&gt;requirements, refinement, planning, build and validation&lt;/strong&gt;. The stages themselves aren't new, but AI can now participate in each one. It can take existing product inputs, help clarify the problem, question assumptions, identify gaps in a PRD and then turn a well-defined requirement into a plan and eventually implementation tasks.&lt;/p&gt;

&lt;p&gt;This puts more emphasis on the quality of the requirements. A human involved in a project might understand what “improve the experience” means because they've had several conversations about it. An agent doesn't have that shared history. It needs the problem, scope, constraints, edge cases and expected outcome to be explicit. That doesn't mean writing enormous specifications; it means using AI to help make the requirements precise before we start building.&lt;/p&gt;

&lt;p&gt;AI can actually be a useful, slightly annoying reviewer here, asking what happens when something fails, whether a requirement is testable, whether two parts of the document contradict each other and what we haven't considered yet. It can also help compare different versions of a PRD or have one model review another's output, making gaps easier to spot. The important part is that AI is helping us uncover ambiguity, not making the decisions for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Maybe coding isn't the bottleneck&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This becomes more interesting when we look at where teams actually spend their time. Complex work can involve several rounds between product, UX, requirements and engineering before development can properly begin. That's often necessary, but it can also create significant bottlenecks. If an AI agent can produce a working implementation quickly, waiting two weeks for a requirement to be clarified becomes a much bigger problem than it used to be.&lt;/p&gt;

&lt;p&gt;This suggests that engineers need to be involved earlier rather than receiving requirements only once they're considered finished. UX needs to be part of the conversation early too. A rough prototype or wireframe can expose gaps in a requirement much faster than another round of discussion, and AI makes creating those lightweight prototypes much cheaper. Requirements, UX and technical design can become an iterative loop rather than a series of handoffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;More documentation isn't always better&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The instinct with AI is often to give it more documentation: more PRDs, more architecture diagrams, more wiki pages and more context. But if several documents describe the same capability differently, we're not really giving the agent better context; we're giving it more ways to get confused.&lt;/p&gt;

&lt;p&gt;What the agent needs is a way to navigate the system. A clear project structure, focused documentation, useful agent instructions, architecture decisions that explain why something exists and a well-organised codebase can be far more useful than one giant specification. The goal isn't to give AI everything we know. It is to make it easy for AI to find what it needs when it needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The ticket might be the problem&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI also makes me question how we slice work. A ticket that takes weeks or months is difficult for an agent to reason about, and arguably isn't a particularly useful unit of work for humans either. “Build authentication” is very different from breaking the problem into password reset, token validation, password updates and the associated tests.&lt;/p&gt;

&lt;p&gt;That doesn't mean turning every feature into dozens of tiny tickets. We just need work that has a clear purpose, manageable scope and a definition of done. If something takes months, it might be a project hiding in a Jira costume.&lt;/p&gt;

&lt;p&gt;At the same time, not everything needs the full AI lifecycle. A large feature may benefit from structured requirements, refinement, planning and validation, while a small BAU change might only need a prompt and a developer. If we apply the same process to everything, we risk replacing one form of bureaucracy with another. The workflow should match the complexity of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The agent needs to see the real system&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There is also a fairly fundamental requirement: &lt;strong&gt;the agent needs to understand the real system&lt;/strong&gt;. Giving it a PRD without access to the relevant codebase means it is still making assumptions. With the code, it can find existing patterns, understand constraints, reuse functionality and spot when a proposed solution doesn't fit.&lt;/p&gt;

&lt;p&gt;Of course, giving AI access to source code introduces security, licensing, privacy and organisational considerations, so AI adoption isn't simply a matter of choosing the right model. Some of the biggest barriers have nothing to do with the model at all.&lt;/p&gt;

&lt;p&gt;And once multiple developers and agents are working in parallel, things get even more interesting. Each agent has its own context and can make decisions based on what it currently sees. One agent can change something another doesn't know about, or two agents can make perfectly reasonable decisions that don't work well together. This makes good engineering practices even more important: small changes, clear boundaries, good tests, frequent reviews and consistent project rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;So, are we ready?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Probably not completely, and that's fine. We don't need to jump straight into autonomous software development. We can take real pieces of work, try these workflows, see where they break and improve the process as we go.&lt;/p&gt;

&lt;p&gt;Because AI isn't just changing how quickly we write code. It is exposing everything around the code that slows us down: unclear requirements, oversized tickets, late UX involvement, documentation drift, handoffs, access restrictions and decisions that take days to make.&lt;/p&gt;

&lt;p&gt;That's why I think the idea of an &lt;strong&gt;AI harness&lt;/strong&gt; is bigger than prompts and agent configuration. The harness is the environment we create around the AI: how we define work, how product, UX and engineering collaborate, how knowledge is structured, how repositories are organised, how work is validated and what access the agent has.&lt;/p&gt;

&lt;p&gt;The AI might be the new part, but the way we work around it is what will determine whether it actually makes us faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI engineering is easy. Changing how we work is hard.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>software</category>
      <category>discuss</category>
    </item>
    <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" width="710" height="399"&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" width="710" height="399"&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" width="710" height="399"&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>
  </channel>
</rss>
