<?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: krishaant S.H</title>
    <description>The latest articles on DEV Community by krishaant S.H (@vikram003).</description>
    <link>https://dev.to/vikram003</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%2F4077427%2F26dfaf22-069e-4525-a6a5-ba9f91f1b22d.png</url>
      <title>DEV Community: krishaant S.H</title>
      <link>https://dev.to/vikram003</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vikram003"/>
    <language>en</language>
    <item>
      <title>Fetch'd: It's Tinder, except the dog can reject you too</title>
      <dc:creator>krishaant S.H</dc:creator>
      <pubDate>Mon, 17 Aug 2026 03:56:11 +0000</pubDate>
      <link>https://dev.to/vikram003/fetchd-its-tinder-except-the-dog-can-reject-you-too-150h</link>
      <guid>https://dev.to/vikram003/fetchd-its-tinder-except-the-dog-can-reject-you-too-150h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Somewhere between 7% and 20% of adopted shelter dogs get returned and often fast: one South Carolina study found returns within three months had a median ownership length of just eight days (&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9100056/" rel="noopener noreferrer"&gt;source&lt;/a&gt;) . Behavior problems are the single biggest driver (36.1% of returns), followed by incompatibility with existing pets (18.3%) (&lt;a href="https://www.nature.com/articles/s41598-021-87649-2" rel="noopener noreferrer"&gt;source&lt;/a&gt;) . The other major factor isn't the dog at all it's mismatched expectations: adopters who returned dogs within three months went in with significantly higher expectations than those who didn't, and two-thirds hit behavioral problems anyway. Worse, a return lowers a dog's odds of being adopted again  a bad match doesn't just fail once, it makes the dog harder to place a second time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual product to build, then, isn't "help people find a cute dog faster." It's close the gap between what an adopter expects and what a specific dog is really like to live with  before the return, not after.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fetch'd is a two-sided swipe app: dogs have profiles (filled in by shelter staff), adopters have profiles, and either side can browse and swipe on the other. A match only forms on a mutual right-swipe the dog's side has to say yes too, not just the adopter. That reflects the core belief behind the product: an algorithm has no business being the final judge of who takes a dog home. It can't foresee a personality click at a first meeting or a kid a dog takes to instantly  the hundred small variables no intake form could ever capture. So Fetch'd never renders a verdict. Every note is a specific, checkable observation, and the real decision stays with the humans holding the leash. Structurally, that means dogs and adopters are symmetric entities in the schema  each with their own swipe rows, not a "users" table and a "listings" table wearing a trenchcoat.&lt;/p&gt;

&lt;p&gt;Every profile card also carries a one-sentence, Gemini-generated compatibility note about the other party grounded, specific, no score. More on that below, since it's most of what I actually spent the weekend building.&lt;/p&gt;

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

&lt;p&gt;Everything holding this up is intentionally plain: React + Vite + TypeScript, Supabase for Postgres/auth/storage with row-level security scoping every table to its owning &lt;code&gt;user_id&lt;/code&gt;, no framework beyond that. Auth is username/password with no real email involved — a chosen username maps to a synthetic &lt;code&gt;username@fetchd.local&lt;/code&gt; address handed to Supabase's normal email/password auth, so sign-up is "pick a username," not "verify your inbox." That's a scope call I'll say plainly rather than bury: there's no password-reset flow, because there's no real inbox to send a reset link to, and no OAuth. Both are exactly what a fuller version needs before this could be a real shelter's system of record, along with things like photo moderation and multi-user shelter accounts. For a weekend, the time went into the two-sided data model and the compatibility-note grounding instead — the parts that make the core idea actually work, not just look like it does on a landing page.&lt;/p&gt;

&lt;p&gt;Here's how that stack fits together:&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%2Fz2k5hp65m0lf13mb8ezo.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%2Fz2k5hp65m0lf13mb8ezo.png" alt="Architecture diagram: browser client, Supabase, and Gemini API" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the same thing broken down to the actual function and table names, if you want the call trace instead of the shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─ Browser  (React + Vite + TS, no backend server) ─────────────────────────┐
│                                                                           │
│ auth.ts        username -&amp;gt; username@fetchd.local, handed to Supabase Auth │
│ CreateProfile  4 photos -&amp;gt; Storage . answers/intake -&amp;gt; Postgres           │
│                intake sheet photo -&amp;gt; base64  ----------------+            │
│ SwipeDeck      combineAnswers() -&amp;gt; labeled text  -----------+ |           │
│                swipe direction -&amp;gt; Postgres                  | |           │
│                                                                           │
└───────────────────────────────────────────────────────────────────────────┘
            |                                          | |
            v                                          | |
┌─ Supabase ──────────────────────────────────────────────────────┐
│                                                                 │
│ Auth       email/password under the hood                        │
│            (no real inbox =&amp;gt; no password-reset flow, by design) │
│ Postgres   dogs . adopters . swipes . matches . messages        │
│            RLS: every row scoped to auth.uid()                  │
│ Storage    photos bucket, public read / authenticated write     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
                                                        | |
                                                        v v
┌─ Gemini API  (gemini-flash-latest, called via fetch() directly from the browser) ─┐
│                                                                                   │
│ generateCompatibilityNote(viewerText, targetText)                                 │
│    one grounded sentence per profile card                                         │
│    rules: grounded only in stated facts . no score or verdict .                   │
│           ask a question on a gap instead of guessing .                           │
│           exact fallback string when neither side has enough detail               │
│                                                                                   │
│ extractIntakeDocument(base64, mimeType)                                           │
│    vision extraction from a photographed/scanned vet intake sheet                 │
│    fixed JSON shape, null for anything not literally on the page                  │
│    -&amp;gt; reviewed by shelter staff in IntakeReviewForm before saving                 │
│                                                                                   │
│                                                                                   │
│                                                                                   │
│                                                                                   │
└───────────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two separate Gemini calls in Fetch'd, and they're deliberately a pipeline, not a grab-bag of "AI features": one turns a photo into structured facts, the other turns two sets of structured facts into one checkable sentence.&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%2Fb0dzsscj256mogml0uob.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%2Fb0dzsscj256mogml0uob.png" alt="Pipeline diagram: vision extraction from intake sheet feeding into grounded compatibility reasoning" width="800" height="1704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call 1 — vision extraction from the intake sheet.&lt;/strong&gt; Shelters already have vet intake paperwork for every dog. &lt;code&gt;extractIntakeDocument&lt;/code&gt; sends a photographed or scanned sheet straight to Gemini with &lt;code&gt;responseMimeType: 'application/json'&lt;/code&gt; and a fixed target shape — weight, spay/neuter status, diagnosed conditions, behavior observations, and a dozen other fields. The rule baked into that prompt is the same grounding discipline as the second call: &lt;em&gt;"Never guess or infer a value that is not literally present on the page,"&lt;/em&gt; with &lt;code&gt;null&lt;/code&gt; (or an empty array) as the required output for anything the document doesn't actually say. The extraction lands in an editable review form before anything touches the database — a shelter worker corrects the model instead of trusting it blind. That matters more than it sounds like it should: this is the data that later gets compared against an adopter's answers, so a hallucinated "high energy" on a couch-potato senior dog doesn't just look wrong, it produces a wrong compatibility note downstream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call 2 — the compatibility note, and the design bet underneath it.&lt;/strong&gt; Once an adopter's five structured answers and a dog's reviewed intake fields both exist, &lt;code&gt;combineAnswers()&lt;/code&gt; formats each into labeled text — &lt;code&gt;"Home &amp;amp; nearby space: house with a small yard. Hours alone on a typical day: 3-4."&lt;/code&gt; for the adopter, a plain-language intake summary for the dog — and both get handed to &lt;code&gt;generateCompatibilityNote&lt;/code&gt;. That prompt runs on four hard rules, not four suggestions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounded only in stated facts.&lt;/strong&gt; Never invent a trait, preference, or fact not literally present in either text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No verdict, no score.&lt;/strong&gt; No "great match," no percentage. The note states a connection; the reader draws the conclusion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask instead of guessing.&lt;/strong&gt; If one profile mentions something specific — noise sensitivity, exercise needs — and the other side hasn't said enough to compare against it, the model turns that gap into a direct question back to the viewer, not a filled-in assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A required, exact fallback.&lt;/strong&gt; If neither profile has enough detail to support a real observation, the output must be exactly &lt;code&gt;"Not enough detail yet to spot a specific connection."&lt;/code&gt; No generic filler standing in for a real one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those rules are backed by real worked examples inside the prompt itself, not just described to the model in the abstract — this is the actual few-shot pair shipped in &lt;code&gt;generateCompatibilityNote&lt;/code&gt;, reformatted here from single-line prompt strings for readability, wording otherwise unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VIEWER'S PROFILE: "I work from home and live in a quiet apartment with no other pets."
PROFILE THEY ARE VIEWING: "Rex is a 4-year-old lab mix, great with people, gets anxious
around loud noises and other dogs."
Output: Rex's noise-sensitivity could work well with your quiet apartment, though it's
worth confirming he'd be comfortable being home during your work calls.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VIEWER'S PROFILE: "I live in a house with a small backyard."
PROFILE THEY ARE VIEWING: "Rex is a high-energy 2-year-old who needs at least an hour
of exercise a day."
Output: Rex needs at least an hour of daily exercise - how much active time could you
realistically give him each day?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two flavors of the same rule set: one produces a flattering-but-specific observation, the other produces a direct question instead of a soft "might not be right for you." That "no score" bet wasn't arbitrary — a percentage invites exactly the kind of blind trust the grounding rules are designed to avoid. Fetch'd skips the score entirely and ships only the explanation. The re-readable, checkable sentence &lt;em&gt;is&lt;/em&gt; the feature — not a caption under a number.&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%2Ffc1a72nget7lvz8q1rlt.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%2Ffc1a72nget7lvz8q1rlt.png" alt="Discover new users" width="799" height="364"&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/DVbqYPodlJE"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;website link: &lt;a href="https://fetchd-theta.vercel.app/login" rel="noopener noreferrer"&gt;https://fetchd-theta.vercel.app/login&lt;/a&gt;&lt;br&gt;
Want to try the live app yourself instead of just watching? Two seeded test accounts, one on each side, so you can see both a dog's view of adopters and an adopter's view of dogs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Username&lt;/th&gt;
&lt;th&gt;Password&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test_dog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adopter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test_adopter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;123456&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Heads up: this runs on the Gemini API free tier, which caps daily requests. If you swipe through a lot of profiles or try the vet-intake OCR upload after the day's quota is spent, the compatibility note or the intake extraction may silently fall back / not populate  that's a quota limit, not a bug.&lt;/p&gt;

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

&lt;p&gt;Repo: [&lt;a href="https://github.com/Krishaant003/fetchd" rel="noopener noreferrer"&gt;https://github.com/Krishaant003/fetchd&lt;/a&gt;]&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compatibility-note prompt&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;src/lib/gemini.ts&lt;/code&gt; — &lt;code&gt;generateCompatibilityNote&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vet-intake vision extractor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;src/lib/gemini.ts&lt;/code&gt; — &lt;code&gt;extractIntakeDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-sided schema + RLS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;supabase/schema.sql&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured-answer formatting&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;src/types.ts&lt;/code&gt; — &lt;code&gt;combineAnswers&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swipe / mutual-match logic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;src/pages/SwipeDeck.tsx&lt;/code&gt;, &lt;code&gt;src/lib/api/swipes.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Submitting to &lt;strong&gt;Best Use of Google AI&lt;/strong&gt;. Both Gemini calls described above are load-bearing, not decorative: &lt;code&gt;extractIntakeDocument&lt;/code&gt; is the vision step that turns a photographed vet intake sheet into structured data, and &lt;code&gt;generateCompatibilityNote&lt;/code&gt; is the grounded reasoning step that turns two profiles into a single checkable sentence  the actual core mechanic the whole app is built around.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
