<?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: Nardeep Singh</title>
    <description>The latest articles on DEV Community by Nardeep Singh (@nardeep_singh_3bccdbebe76).</description>
    <link>https://dev.to/nardeep_singh_3bccdbebe76</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%2F4138714%2Fa3165790-22d5-441c-9358-3ef0dbbf5aab.png</url>
      <title>DEV Community: Nardeep Singh</title>
      <link>https://dev.to/nardeep_singh_3bccdbebe76</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nardeep_singh_3bccdbebe76"/>
    <language>en</language>
    <item>
      <title>Steadywag: I Vibe-Coded a Dog Care App Where Missing Paperwork Is Data</title>
      <dc:creator>Nardeep Singh</dc:creator>
      <pubDate>Sun, 04 Oct 2026 21:33:21 +0000</pubDate>
      <link>https://dev.to/nardeep_singh_3bccdbebe76/steadywag-i-vibe-coded-a-dog-care-app-where-missing-paperwork-is-data-1pni</link>
      <guid>https://dev.to/nardeep_singh_3bccdbebe76/steadywag-i-vibe-coded-a-dog-care-app-where-missing-paperwork-is-data-1pni</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Steadywag&lt;/strong&gt; is a care companion for a dog with a chronic illness, and the strange part is what it is about: &lt;strong&gt;the things his paperwork does not say.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My dog Theo is an 8-year-old Shih Tzu mix with copper storage liver disease, recurrent pancreatitis and high triglycerides. His care lives in about 180 pages of PDFs, emails and things said out loud in exam rooms. One medication is still on his written list, but a specialist told us in person to stop it, and no document says so. A sitter, an emergency vet or a chatbot reading that list would get it wrong.&lt;/p&gt;

&lt;p&gt;So I built an app where absence is data. A Sanity record can say a medication is "listed, not given," that a fact is "conflicting" across two reports, or that a lab result a specialist promised never arrived. An agent reads all of it, and the same record drives a day plan, a sitter brief, a daily check-in log, an appointments calendar and a vet-visit prep page. It installs as an app on a phone or a computer.&lt;/p&gt;

&lt;p&gt;It is for families who care for a sick pet, and first of all for mine.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://steadywag.com" rel="noopener noreferrer"&gt;https://steadywag.com&lt;/a&gt; (the original &lt;a href="https://steadywag.vercel.app" rel="noopener noreferrer"&gt;https://steadywag.vercel.app&lt;/a&gt; also works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video walkthrough:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=LfrX6UZ_XJU" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=LfrX6UZ_XJU&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/LfrX6UZ_XJU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Try these on the first screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;What does he need today?&lt;/em&gt; It opens with the medication that is listed but never given.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;I'm watching Theo.&lt;/em&gt; It writes a sitter brief you can print.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Can he have blueberries?&lt;/em&gt; It answers with a verdict, then shows its checks against his own diet plan.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Can he have mango?&lt;/em&gt; His plan does not cover it, so it looks up USDA numbers and labels them "not from his vet."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app limits questions each day to keep the demo running. Every other page works without the assistant.&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/narlynars07" rel="noopener noreferrer"&gt;
        narlynars07
      &lt;/a&gt; / &lt;a href="https://github.com/narlynars07/steadywag" rel="noopener noreferrer"&gt;
        steadywag
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A care companion for a dog with a chronic illness. An agent on Sanity Context that tells you what the records don't say. DEV Sanity Challenge, Path One.
    &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;Steadywag&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A care companion for a dog with a chronic illness.&lt;/strong&gt; It reads his records and tells you what the paperwork doesn't say.&lt;/p&gt;
&lt;p&gt;Built on one real case: Theo, an 8-year-old Shih Tzu mix with copper storage hepatopathy, recurrent pancreatitis and high triglycerides. His care lived in about 180 pages of reports, emails and things said out loud in exam rooms. Steadywag turns that into one structured, sourced record in Sanity, then puts an agent, a daily plan, check-ins and appointments on top of it. Built for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16" rel="nofollow"&gt;DEV Sanity Challenge&lt;/a&gt;, Path One: an agent that queries real content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The idea in one example.&lt;/strong&gt; A medication was on the written list, but the dog never got it: a specialist told one caregiver out loud to skip it, and nobody updated the paper. The structure knows (&lt;code&gt;medication.status = "listed-not-given"&lt;/code&gt; plus a &lt;code&gt;recordGap&lt;/code&gt;). The PDFs don't. The agent, the…&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/narlynars07/steadywag" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The setup
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;Claude Code&lt;/strong&gt; in a terminal as my AI IDE, plus &lt;strong&gt;Claude in Chrome&lt;/strong&gt; to drive a real browser so it could look at its own work. I direct the product. I did not write the code. That meant my job was deciding what to build, supplying the facts only a family knows, and approving every step that touches real data or goes public.&lt;/p&gt;

&lt;p&gt;The working rules I set early, and kept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plan, then wait.&lt;/strong&gt; For anything that was a real decision (stack, storage, what is public), Claude Code had to lay out the choice and wait for my answer. I never let it present a decision as settled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing loads without approval.&lt;/strong&gt; The dataset was built by script, dry-run, then loaded only after I said so. The family's own observations sat behind a flag that stayed off until I approved them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check before claiming.&lt;/strong&gt; Before it told me something was fixed, it had to show me: a screenshot at a real screen width, a query result, a test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final repo has 40 commits over about two days, around 8,900 lines of app code and 1,300 lines of Sanity schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompts that worked
&lt;/h3&gt;

&lt;p&gt;Short, specific, and usually a reaction to something I could see. These are real messages from my session, typos left in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"how can we make the agent more useful… separate knowledge base? web search? guardrails?"&lt;/em&gt; This one reshaped the agent. It came back with verdict-first food answers ("Fits his plan's rules," "Probably not," "No," "Can't tell"), a small scope gate, and a web search limited to a short list of veterinary sources.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"can you rate this answer? he didnt find the label information"&lt;/em&gt; (with a screenshot). Claude rated its own answer honestly: it had implied it could search for a product's label, and it cannot. The fix was a rule: ask for the ingredient list instead of pretending.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"is this saved only on the browser because it's a demo, should we explain it in the video?"&lt;/em&gt; This surfaced a design decision I had never made out loud. Check-ins and appointments stay in the visitor's browser on purpose, because they are private health details. Now every page says so at the top.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"on the today section the card with the info about the medication that we are not giving him takes most of the screen can that card be minimizes"&lt;/em&gt; A one-line complaint with a screenshot. The card went from about 540px to 155px, with the warning still always visible.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"can we do a security check make sure we didnt leave any persinal information, or api keys or tokens open"&lt;/em&gt; It scanned the repo, all of git history, the live dataset and the deployed code for 34 real emails, phone numbers and ID numbers from my private files. It found none. It also found two real issues I had not asked about: raw visitor IP addresses in the rate limiter, and missing browser security headers. Both are fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prompts and approaches that didn't work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Can you search for this treat's label?"&lt;/strong&gt; The agent talked as if it could. A model that sounds helpful will fill a gap with a guess, and a guess about a dog's food is the wrong kind of helpful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard-coding data in the page.&lt;/strong&gt; Early on, the diets from before his diagnosis lived in the page instead of Sanity, so the agent could not see them. It answered a plain question wrongly. That was my build's fault, not the model's.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;npm audit fix --force&lt;/code&gt;.&lt;/strong&gt; GitHub's Dependabot raised 41 alerts. The suggested fix downgraded two core packages to old major versions. Claude Code read the audit, traced which alerts could reach the live site (none; all were in Sanity's command-line tooling and the linter), and tested overrides on a separate branch first. One override left the install in a broken state, so it rolled that one back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A background test run.&lt;/strong&gt; A long audit I started in the background only advanced while the model was actively working. I stopped it and ran the rest in the foreground.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where the model got stuck, and how we got out
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Blank answers.&lt;/strong&gt; "What does he need today?" and the sitter brief came back empty. The agent spent all 8 of its tool steps on lookups and never wrote an answer. The fix was a structural one, not a prompt: raise the cap to 12 and make the final step text-only, so an answer always gets written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The desktop chat window cut off answers.&lt;/strong&gt; This took three stacked fixes. First the page scrolled instead of the chat panel. Then the card stopped being a flex container once a thread opened. Then its grid row was unconstrained. Each fix revealed the next. The only way out was to measure: a long real answer, in a frame at exact desktop widths, until nothing was clipped. The same bug came back later in a different form (Theo's photo clipped at the top of a short window). That time the root cause was centering content in a box shorter than the content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The filter trap, twice.&lt;/strong&gt; Sanity Context's chart endpoint only sees the document types in its filter, and it reads the schema from the hosted Studio. When I added new types, the agent could not see them. It said, honestly, "I couldn't retrieve his family routine." That honest failure is how we found the problem, both times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Knowledge Base invented a number.&lt;/strong&gt; It added a "400-600 borderline" row to a liver-copper table that no source gives, and the agent repeated it. The fix was a standing instruction on the Knowledge Base: do not add reference ranges that no cited source states. The same Knowledge Base also read a date written "3/10" as March 3 instead of March 10. I now spell out months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Environment variables that looked empty.&lt;/strong&gt; Vercel marks production variables as "sensitive" by default, and they pull back empty. Claude Code read that as "not set," and spent a while on a problem that did not exist. The fix was to set them as non-sensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. My own security header broke my own test.&lt;/strong&gt; After the security pass added a header that stops other sites from embedding the app, the layout test Claude Code had been relying on (loading the app in a frame at a phone width) stopped working. That is the header doing its job. I switched to resizing the panel directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. The first audit.&lt;/strong&gt; I ran 12 questions through the agent. 4 failed: blueberries, the pre-diagnosis diet, the copper reference, and an Atopica date. Every fix was a change to the data or an instruction, never a prompt trick. The audit now lives in the repo as &lt;code&gt;evals/&lt;/code&gt;: 24 questions, each with the reason it exists, and a runner. They pass against the live site, and the README says plainly that a pass means "avoided the known traps," not "good."&lt;/p&gt;

&lt;h3&gt;
  
  
  What was mine, and what was Claude Code's
&lt;/h3&gt;

&lt;p&gt;Claude Code wrote the code, the schema, the scripts and most of the first drafts of the records. I supplied what no document holds: the verbal instruction about the medication, the family's real daily routine, what we noticed before each flare. I also did every step in the Sanity dashboard that the AI could not do for me. Creating the Context endpoints and the Knowledge Base is a dashboard job, so Claude Code wrote me a click-by-click setup sheet (&lt;code&gt;docs/context-setup.md&lt;/code&gt;) and I followed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The App SDK and Workflows
&lt;/h3&gt;

&lt;p&gt;I did not use the App SDK or Sanity Workflows, and I would rather say so than stretch. The closest thing is a change log stored as data (&lt;code&gt;recordUpdate&lt;/code&gt;). When his vet changes the written list, I mark the medication stopped in the Studio, resolve its gap, and add a log entry with who said so. Within about a minute the home page, Today, the sitter brief and the agent all update, because they read the same record. That is a review step encoded in documents, not a Sanity Workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I would do differently
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Write the audit questions on day one, not after the first failures.&lt;/li&gt;
&lt;li&gt;Model the sources first. Once the vet's words and the family's routine were separate documents, the agent stopped blending them.&lt;/li&gt;
&lt;li&gt;Run the security scan from the start, not at the end.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;yahsq70q&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; (public read)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset query URL:&lt;/strong&gt; &lt;code&gt;https://yahsq70q.api.sanity.io/v2026-10-01/data/query/production?query=count(*)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Studio:&lt;/strong&gt; &lt;a href="https://steadywag.sanity.studio" rel="noopener noreferrer"&gt;https://steadywag.sanity.studio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Schema:&lt;/strong&gt; 22 document types and one shared object, 516 documents in all (288 lab results, 26 visits, 26 weights, 18 medications). The design choices I would point a reviewer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sourceNote&lt;/code&gt; on every fact from a record: the kind of document, its date, and a confidence of &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;single-source&lt;/code&gt; or &lt;code&gt;conflicting&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recordGap&lt;/code&gt;: documents for what the chart does &lt;strong&gt;not&lt;/strong&gt; know. Absence cannot be searched for, so it has to be modeled. There are 9 open gaps.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;medication.status = "listed-not-given"&lt;/code&gt; and &lt;code&gt;medication.writtenInstruction&lt;/code&gt;, kept apart from &lt;code&gt;careRoutine&lt;/code&gt;, the family's own daily times. The vet's words and the family's routine are different sources, and the agent labels each one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;historyChapter&lt;/code&gt; and &lt;code&gt;historyPattern&lt;/code&gt;, which hold references to the visits, labs and medications they rest on, so every claim points at its evidence.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recordUpdate&lt;/code&gt;, the change log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent reads the dataset through two Sanity Context endpoints (a GROQ-mode chart endpoint and a Knowledge Base endpoint built from 26 documents of cited guidance and diet rules), read-only, with Claude Sonnet 5.5.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Steadywag tracks and prepares. It never diagnoses, doses or replaces his vet, and it is not medical advice. Theo's records are de-identified: first name and birth year only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
    <item>
      <title>Steadywag: A Care Agent for Dogs with Chronic Illness, Built on One Real Case</title>
      <dc:creator>Nardeep Singh</dc:creator>
      <pubDate>Sun, 04 Oct 2026 16:45:56 +0000</pubDate>
      <link>https://dev.to/nardeep_singh_3bccdbebe76/steadywag-a-care-agent-that-knows-what-his-paperwork-gets-wrong-46ec</link>
      <guid>https://dev.to/nardeep_singh_3bccdbebe76/steadywag-a-care-agent-that-knows-what-his-paperwork-gets-wrong-46ec</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path One: Ship an Agent That Queries Real Content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A medication was on the written list. The dog never got it.&lt;/p&gt;

&lt;p&gt;A specialist told one caregiver out loud to skip it. Nobody updated the paper. A second caregiver covering his meds would reasonably think it was current, because every document said so.&lt;/p&gt;

&lt;p&gt;That gap is the whole project.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Steadywag&lt;/strong&gt; is a care companion for a dog with a chronic illness, built on one real case: Theo, an 8-year-old Shih Tzu mix with copper storage hepatopathy, recurrent pancreatitis and high triglycerides. His care lives in about 180 pages of PDFs, emails and things said out loud in exam rooms.&lt;/p&gt;

&lt;p&gt;I pulled it into one structured chart in Sanity, then built the app on top of it. It installs on a phone or a computer, works offline for the pages you have opened, and follows light or dark mode.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask.&lt;/strong&gt; Pick a task: &lt;em&gt;What does he need today? Something changed. Can he eat this? I'm watching Theo&lt;/em&gt; (an instant sitter brief you can print). &lt;em&gt;Prep my vet visit&lt;/em&gt; (with your own check-ins). Or ask anything. While the agent works, you watch it: "Reading his medication list," "Reading his family routine." When it finishes, source chips link to the pages it used, and you can keep the conversation going with follow-up questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today.&lt;/strong&gt; The day as a timeline, built from data with no agent call. It follows the clock: "Next up: dinner at 8 PM," then "Later today," with finished steps collapsed. His vet's written instruction comes first. The family's own routine fills in a time only where the vet gave none, and every time is labeled. The medication that is listed but not given sits at the top, folded to one line so it does not take over the screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check-in.&lt;/strong&gt; A 30-second daily log of the things his vet asks about at every visit: eating, drinking compared with usual, energy, stool (picked from pictures), vomiting, meds, activity, and a body check for bruising or a yellow tint in the eyes, ear flaps or gums. Before a visit it counts them across days: "drinking more than usual on 2 of 3 days," counting only the days a question was answered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appointments.&lt;/strong&gt; Vet, grooming and dental visits, with a month calendar and an "Add to my calendar" file that includes a reminder the day before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History.&lt;/strong&gt; "Theo in 60 seconds," one chart with ALT, medication periods and flares on a shared axis, five chapters and four patterns. Each claim points at the records it rests on. A "What changed" log and his DNA test report sit alongside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your dog.&lt;/strong&gt; A pre-diagnosis diet questionnaire, so families can give researchers consistent dietary copper data. It starts blank. Answers never leave the visitor's browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the agent will and won't do
&lt;/h3&gt;

&lt;p&gt;It never diagnoses, never gives or converts a dose, and never tells you to start or stop anything. It reads. It cannot write.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verdict first.&lt;/strong&gt; For "can he have X," it opens with one verdict: &lt;em&gt;Fits his plan's rules&lt;/em&gt;, &lt;em&gt;Probably not&lt;/em&gt;, &lt;em&gt;No&lt;/em&gt;, or &lt;em&gt;Can't tell&lt;/em&gt;. Then it shows the checks against his own plan: copper compared with the foods already approved, sodium, fat, calories against the treat limit, plain preparation, and his avoid list. It never says "safe," always says when his vet has not confirmed it, and ends with a question to ask his vet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two narrow reaches outside his chart.&lt;/strong&gt; For a food his plan does not cover, it looks up copper, sodium and fat in USDA FoodData Central, labeled "USDA food data, not from his vet." For general questions it can run a web search, at most twice per answer, limited to a short allowlist of veterinary and nutrition sites. Anything from the web is labeled "From the web" with a link, and nothing about medication or doses is taken from it. His vet's plan always wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asks for the ingredients.&lt;/strong&gt; If someone asks about a store-bought product, it does not pretend to search for the label. It asks for the ingredient list and checks that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stays on topic.&lt;/strong&gt; A small, fast model checks that a free question is about Theo, dogs or the app. Anything else, like math, a poem or "ignore your instructions," is declined in one sentence in about half a second.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://steadywag.com" rel="noopener noreferrer"&gt;https://steadywag.com&lt;/a&gt; (the original &lt;a href="https://steadywag.vercel.app" rel="noopener noreferrer"&gt;https://steadywag.vercel.app&lt;/a&gt; also works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video walkthrough:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=LfrX6UZ_XJU" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=LfrX6UZ_XJU&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/LfrX6UZ_XJU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Try these on the Ask tab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;What does he need today?&lt;/em&gt; It opens with the medication that is on the list but never given.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;I'm watching Theo.&lt;/em&gt; It opens an instant sitter brief that starts with the medication the paperwork gets wrong, and ends with a reminder to add phone numbers.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Are his freeze-dried treats allowed?&lt;/em&gt; His records disagree. The answer shows both sides and refuses to pick one.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Can he have mango?&lt;/em&gt; His plan does not cover it, so you can watch it look up the USDA numbers and compare them with his approved foods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On every page except Ask, a floating chat button opens the same agent in a window, so you can ask while you read.&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/narlynars07" rel="noopener noreferrer"&gt;
        narlynars07
      &lt;/a&gt; / &lt;a href="https://github.com/narlynars07/steadywag" rel="noopener noreferrer"&gt;
        steadywag
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A care companion for a dog with a chronic illness. An agent on Sanity Context that tells you what the records don't say. DEV Sanity Challenge, Path One.
    &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;Steadywag&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;A care companion for a dog with a chronic illness.&lt;/strong&gt; It reads his records and tells you what the paperwork doesn't say.&lt;/p&gt;
&lt;p&gt;Built on one real case: Theo, an 8-year-old Shih Tzu mix with copper storage hepatopathy, recurrent pancreatitis and high triglycerides. His care lived in about 180 pages of reports, emails and things said out loud in exam rooms. Steadywag turns that into one structured, sourced record in Sanity, then puts an agent, a daily plan, check-ins and appointments on top of it. Built for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16" rel="nofollow"&gt;DEV Sanity Challenge&lt;/a&gt;, Path One: an agent that queries real content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The idea in one example.&lt;/strong&gt; A medication was on the written list, but the dog never got it: a specialist told one caregiver out loud to skip it, and nobody updated the paper. The structure knows (&lt;code&gt;medication.status = "listed-not-given"&lt;/code&gt; plus a &lt;code&gt;recordGap&lt;/code&gt;). The PDFs don't. The agent, the…&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/narlynars07/steadywag" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Next.js 16 and the Vercel AI SDK on the web side. A Sanity Studio and a 22-type schema in &lt;code&gt;studio/&lt;/code&gt;. The README explains the architecture and how to run it, and &lt;code&gt;evals/&lt;/code&gt; holds the audit questions described below, with a runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Sanity
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The structure does the work.&lt;/strong&gt; Here is what a keyword search over the original PDFs could not tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;medication.status = "listed-not-given"&lt;/code&gt; plus a &lt;code&gt;recordGap&lt;/code&gt; of kind &lt;code&gt;verbal-instruction&lt;/code&gt;. The PDFs show the drug as active. The fact that it was never given exists only because the family told me.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sourceNote.confidence&lt;/code&gt; on every fact from a record: &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;single-source&lt;/code&gt;, or &lt;code&gt;conflicting&lt;/code&gt;. A conflict is a queryable state, not a sentence buried in a note. Cerenia is one: his medication list says Monday, Wednesday, Friday, and the written instruction in the same report says every 24 hours as needed. Both stay visible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recordGap&lt;/code&gt; documents. Absence cannot be searched for. It has to be modeled. The chart holds 9 open gaps, like the lab result a specialist said was pending that never arrived.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dietHistoryEntry.origin&lt;/code&gt;: &lt;code&gt;family-recall&lt;/code&gt; or &lt;code&gt;medical-record&lt;/code&gt;. The agent has to say which one it is quoting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;medication.writtenInstruction&lt;/code&gt; next to a separate &lt;code&gt;careRoutine&lt;/code&gt; type. The vet's words and the family's routine are different sources. The agent labels each one: &lt;strong&gt;Vet records, Family routine, Family recall, Family observations, Your check-ins, From the web&lt;/strong&gt;. When the vet gave a time, it uses it. The routine never overrides it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;familyNote&lt;/code&gt; documents: what one family has noticed, like the first signs of a flare. They say whether his vet directed them, and the agent is told never to turn them into advice. Medication timing and doses are kept out.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;historyChapter&lt;/code&gt; and &lt;code&gt;historyPattern&lt;/code&gt; documents, each holding references to the visits, labs and medications it rests on. The History page and the agent read the same documents.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dnaReport&lt;/code&gt;: his consumer DNA test exactly as the report states it. Putting it next to the guidance gave the agent a cross-source answer: the report has no copper-related test, and none of his breeds is among those the copper guidance names. Both facts are published and labeled. Kit numbers and personal links are never stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dataset is public: 516 documents across 22 types, including 288 lab results, 26 visits and 26 weights. ALT went from 2,431 in December 2023 to 59 in August 2026, and you can chart that from the raw data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two Sanity Context endpoints.&lt;/strong&gt; The agent connects to both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;chart endpoint&lt;/strong&gt; in GROQ mode over the dataset. Tools: &lt;code&gt;groq_query&lt;/code&gt;, &lt;code&gt;schema_explorer&lt;/code&gt;, &lt;code&gt;array_field_reader&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;knowledge endpoint&lt;/strong&gt; backed by a &lt;strong&gt;Knowledge Base&lt;/strong&gt;. I pointed it at the dataset with one GROQ query, &lt;code&gt;*[_type == "guidance" || _type == "dietRule"]&lt;/code&gt;, which matches 26 documents: the 10 cited &lt;code&gt;guidance&lt;/code&gt; entries and the 16 &lt;code&gt;dietRule&lt;/code&gt; documents. Sanity builds entries from them and flags conflicts in an Issues view. Tools: &lt;code&gt;knowledge_base_search&lt;/code&gt;, &lt;code&gt;knowledge_base_read&lt;/code&gt;. What the agent does with what it retrieves: it quotes the published guidance next to his own plan, names the source, and says when the evidence is not about his breed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Knowledge Base earned its place, and it also failed in two instructive ways. It gave the agent published guidance to cite next to his own plan, like the ACVIM consensus statement on copper-associated hepatitis. But it also &lt;strong&gt;invented a "400-600 borderline" row&lt;/strong&gt; in a liver-copper threshold table that no source gives, and the agent repeated it. And its conflict check read a date written "3/10" as 3 March. I fixed the first with a standing Knowledge Base instruction and the second by spelling out months in the source text.&lt;/p&gt;

&lt;p&gt;The agent is Claude Sonnet 5.5 with up to 12 tool steps and read-only access. Each task adds its own instructions on top of the base rules. For "Something changed," warning signs come first, and past flares are framed as "During past flares, his records show..." and never as instructions for now. It looks things up first and cites what it used. If the Context endpoints are unreachable it falls back to querying the same public dataset directly, but never silently: the answer shows a "Direct query, not Sanity Context" notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visitor data stays out of the dataset.&lt;/strong&gt; Check-ins and appointments live in the browser. That is deliberate: they are private health details, and saving them on a server would need accounts, because without sign-in every visitor's entries would land in one shared pile. Each page says "Saved on this device only," and a backup file moves them to another device. When you ask for a visit summary, the browser sends the entries since his last visit in a validated, size-capped field. They go to the model marked as family-entered observations, inside delimiters, as data. Nothing is stored server-side, and the daily question limit keeps only a one-way hash of the visitor's address, never the address.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it broke, and how I tested it.&lt;/strong&gt; I tested hard, and the questions are in the repo.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;evals/&lt;/code&gt; holds 24 audit questions, each with the reason it exists and a few checkable expectations, plus a runner (&lt;code&gt;node evals/run.mjs&lt;/code&gt;). They are smoke checks, not a grade, so I read the answers too. Two rounds caught real problems:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Round one: 12 questions, 4 failed.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Blueberries.&lt;/strong&gt; The agent said his plan does not name them. His nutrition consult lists them as an approved treat. My food documents carried no source note, so the agent could not tell where the plan came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What he ate before diagnosis.&lt;/strong&gt; The agent could not see the answer. I had hard-coded those diets in the page instead of storing them in Sanity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liver copper.&lt;/strong&gt; The invented Knowledge Base row, above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atopica.&lt;/strong&gt; One answer claimed nothing was on file after 2024. The data was there. My query never asked for the confirmation date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Round two, after the rebuild.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty answers.&lt;/strong&gt; "What does he need today?" and the sitter brief came back blank. The agent spent all 8 steps on lookups and never wrote. I raised the cap to 12 and made the final step text-only, so an answer always gets written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut-off answers.&lt;/strong&gt; Long briefs stopped mid-sentence and lost their closing line. I raised the output limit and capped the sitter brief at about 350 words.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same filter trap, twice.&lt;/strong&gt; The chart endpoint filters by document type. My new routine and history types were invisible to the agent until I added them, and so were the family notes. It said, correctly, "I couldn't retrieve his family routine." That honest failure is how I found it both times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that I found two more by using it, not by test: a store-bought product answer where the agent talked as if it could read a label, and web results that read as if they came from his vet. Those became the ingredient-list rule and the "From the web" labels.&lt;/p&gt;

&lt;p&gt;The final run passes all of it through Sanity Context. No answer diagnosed, gave a dose, invented a time or named a clinic or person. Median answer time was about 18 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A record that stays true.&lt;/strong&gt; A record is only useful if it changes when the facts change. If his vet removes ursodiol from the written list, the owner marks it stopped in the Studio, resolves its gap and its question, and adds an entry to the &lt;strong&gt;What changed&lt;/strong&gt; log, with who said so. Within about a minute the home page callout, Today, the sitter brief, Visit prep and the agent's answers all stop flagging it, because every one of them reads the same record. Nothing on the website edits the record from the browser, so every change has a person and a source behind it. The steps are in &lt;code&gt;docs/keeping-the-record-current.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity Project Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project ID:&lt;/strong&gt; &lt;code&gt;yahsq70q&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; (public read)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset query URL:&lt;/strong&gt; &lt;code&gt;https://yahsq70q.api.sanity.io/v2026-10-01/data/query/production?query=count(*)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Studio:&lt;/strong&gt; &lt;a href="https://steadywag.sanity.studio" rel="noopener noreferrer"&gt;https://steadywag.sanity.studio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context endpoints:&lt;/strong&gt; two (chart in GROQ mode, knowledge over a Knowledge Base of 26 documents)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document types:&lt;/strong&gt; 22, including &lt;code&gt;dnaReport&lt;/code&gt;, &lt;code&gt;recordUpdate&lt;/code&gt; (the change log), &lt;code&gt;careRoutine&lt;/code&gt;, &lt;code&gt;familyNote&lt;/code&gt;, &lt;code&gt;historySummary&lt;/code&gt;, &lt;code&gt;historyChapter&lt;/code&gt; and &lt;code&gt;historyPattern&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would tell another builder
&lt;/h2&gt;

&lt;p&gt;Model the sources, not just the facts. Once the vet's words and the family's routine were separate documents, the agent stopped blending them.&lt;/p&gt;

&lt;p&gt;Keep the gaps. They are the product.&lt;/p&gt;

&lt;p&gt;Embeddings are not enabled on this dataset. Retrieval from the Knowledge Base is keyword search, so it only works because the content behind it is structured and cited.&lt;/p&gt;

&lt;p&gt;The guidance entries are summaries in my own words with a link to every original. A veterinarian has not reviewed them yet, and the site says so.&lt;/p&gt;

&lt;p&gt;The records are de-identified: first name and birth year only, no clinic, clinician, owner or account details.&lt;/p&gt;

&lt;p&gt;His medical record is shared: it lives in Sanity, so every device sees the same thing. His check-ins and appointments stay in your own browser on purpose. In a real product, sign-in and a private database would sync them. I kept the demo private instead of shared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credits.&lt;/strong&gt; Nutrition numbers come from USDA FoodData Central. The guidance entries summarize published veterinary sources, each linked in the dataset, including the ACVIM consensus statement on copper-associated hepatitis. Built with Next.js, the Vercel AI SDK, Claude, Sanity Studio and Sanity Context. Steadywag tracks and prepares. It is not medical advice.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
