<?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: Faris </title>
    <description>The latest articles on DEV Community by Faris  (@faris-khan).</description>
    <link>https://dev.to/faris-khan</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%2F4112006%2Fc337d045-2c5b-424a-8012-aa27f22ea1eb.png</url>
      <title>DEV Community: Faris </title>
      <link>https://dev.to/faris-khan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/faris-khan"/>
    <language>en</language>
    <item>
      <title>Pothole Adoption Agency</title>
      <dc:creator>Faris </dc:creator>
      <pubDate>Sun, 04 Oct 2026 20:46:15 +0000</pubDate>
      <link>https://dev.to/faris-khan/kk-3ig0</link>
      <guid>https://dev.to/faris-khan/kk-3ig0</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;p&gt;&lt;strong&gt;Every pet in this shelter is a real pothole complaint filed with NYC 311 in Queens Community Board 13. Its fate follows&lt;br&gt;
the city's own record: repaired, "no pothole found", transferred, or still waiting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo (no login):&lt;/strong&gt; &lt;a href="https://pothole-shelter.vercel.app" rel="noopener noreferrer"&gt;https://pothole-shelter.vercel.app&lt;/a&gt; · &lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/syncaimain/pothole-shelter" rel="noopener noreferrer"&gt;https://github.com/syncaimain/pothole-shelter&lt;/a&gt; ·&lt;br&gt;
&lt;strong&gt;Sanity project:&lt;/strong&gt; &lt;code&gt;fixjy07h&lt;/code&gt; (dataset &lt;code&gt;production&lt;/code&gt;, public-read)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measured on the live dataset, 4 October 2026&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complaints synced&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2,512&lt;/strong&gt;: every Queens CB 13 street pothole from the last 180 days, plus every one still open. Nothing sampled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lost strays (no coordinates in the record)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,555 (62%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outcomes&lt;/td&gt;
&lt;td&gt;668 adopted · 409 ghosts · 449 transferred · &lt;strong&gt;963 feral&lt;/strong&gt; · 18 in the shelter · 5 unmapped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median time from report to repair&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.9 days&lt;/strong&gt; (653 repaired complaints)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unmapped resolution phrases&lt;/td&gt;
&lt;td&gt;1 phrase, 5 pets, shown publicly instead of guessed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second sync over the same data&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0 created · 0 updated · 0 status events&lt;/strong&gt; (incremental &lt;em&gt;and&lt;/em&gt; forced full)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflows (Sanity Workflows engine)&lt;/td&gt;
&lt;td&gt;3 definitions deployed · 981 live lifecycle instances · 366 cluster reviews · adoption moderation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests, all in CI&lt;/td&gt;
&lt;td&gt;98 unit and workflow tests (20 on the Workflows test bench) · 26 Playwright judge-path + axe tests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A shelter where the animals are potholes. Browse them, read their stories, follow the one that's been waiting since 2021,&lt;br&gt;
visit the ghost hall, and leave one an adoption note. It's for anyone who has ever reported a pothole and wondered what&lt;br&gt;
happened next, and for anyone who likes their civic data with a bit of heart.&lt;/p&gt;

&lt;p&gt;The rule I never broke: &lt;strong&gt;nothing about a pet is made up, and nothing about its fate is decided by me.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The city record decides.&lt;/strong&gt; A deterministic sync maps each 311 record to an outcome. Closed complaints are matched
&lt;em&gt;exactly&lt;/em&gt; against &lt;code&gt;resolutionMapping&lt;/code&gt; documents that a person wrote, one per phrase the city actually uses. Wording
nobody has mapped goes to a public &lt;strong&gt;Unmapped&lt;/strong&gt; bucket instead of being guessed. Open complaints turn &lt;strong&gt;feral&lt;/strong&gt; after 60 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data's own gaps become characters.&lt;/strong&gt; 62% of complaints have no coordinates: they describe a stretch of street
between two cross streets, not a point. Those are the &lt;strong&gt;lost strays&lt;/strong&gt;, listed by street and never placed on a map I'd
have to invent. When an inspector finds nothing, the pet becomes a &lt;strong&gt;ghost&lt;/strong&gt;. The 963 ferals are the city's real backlog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bios restate facts only.&lt;/strong&gt; Each bio starts from a fact template: the date it was reported, the street, and the city's
resolution note &lt;em&gt;quoted verbatim&lt;/em&gt;. Gemini may restyle it, but a fact guard rejects any changed date, number or street, a
reworded city note, or anything that mocks people. House numbers never appear, and coordinates are rounded to about 100 m.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celebrate repairs, never mock anyone.&lt;/strong&gt; The joke is the shelter, not the people who report potholes or the crews who
fix them. When the Department of Transportation repairs one, the pet is "adopted into forever pavement" and the street
gets a congratulations.&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%2Fo13d1qc7lwyr67k9e1cq.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%2Fo13d1qc7lwyr67k9e1cq.png" alt="The Pothole Adoption Agency gallery: shelter totals by outcome above a grid of pet cards, each showing the pet's name, outcome badge, street and report date" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci0h4ol36c8as47zo4o0.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%2Fci0h4ol36c8as47zo4o0.png" alt="The Feral of the week card: one long-waiting pothole, picked by the calendar week, with how long its city record has been open" width="797" height="121"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://pothole-shelter.vercel.app" rel="noopener noreferrer"&gt;https://pothole-shelter.vercel.app&lt;/a&gt; (no login anywhere on the public site)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/y3JjbC-fPN8" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The narration is synthetic: each caption line was voiced with Gemini TTS (&lt;code&gt;gemini-3.8-flash-tts&lt;/code&gt;) by &lt;code&gt;docs/media/make-voiceover.mjs&lt;/code&gt;, so voice and captions say the same words at the same moments.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The judge path:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://pothole-shelter.vercel.app/?outcome=feral&amp;amp;age=years" rel="noopener noreferrer"&gt;gallery&lt;/a&gt; and click a Feral pet.&lt;/li&gt;
&lt;li&gt;Read its complaint history and follow the link to the &lt;strong&gt;real 311 record&lt;/strong&gt; on NYC Open Data.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://pothole-shelter.vercel.app/strays" rel="noopener noreferrer"&gt;lost strays&lt;/a&gt; and the &lt;a href="https://pothole-shelter.vercel.app/ghosts" rel="noopener noreferrer"&gt;ghost hall&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Leave an adoption note on any shelter or feral pet. You'll see "awaiting moderation". The note stays private until a
person approves it in the Shelter Office.&lt;/li&gt;
&lt;li&gt;Open the &lt;a href="https://pothole-shelter.vercel.app/sync" rel="noopener noreferrer"&gt;sync log&lt;/a&gt; for the last sync time, the run log and the unmapped phrases.&lt;/li&gt;
&lt;/ol&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%2Fqvdeu42ia682mkodo5o0.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%2Fqvdeu42ia682mkodo5o0.png" alt="A feral pet's page: its name, outcome and temperament badges, a bio that restates only facts from its 311 record, its street and community board, and a map pin rounded to about 100 metres" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fojyyszhduytd2779sfid.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%2Fojyyszhduytd2779sfid.png" alt="A pet's status timeline, where every change cites the 311 field that caused it, above its complaint history with the city's resolution note quoted word for word" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faj8n1jlqi43v7x0c0zp3.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%2Faj8n1jlqi43v7x0c0zp3.png" alt="The pet's original NYC 311 record as raw JSON from the NYC Open Data API, opened from the link in its complaint history" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhcaudx713jmui7fbimrb.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%2Fhcaudx713jmui7fbimrb.png" alt="Map of every pet with coordinates in Queens Community Board 13: dots coloured by outcome, running along the Queens–Nassau border" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flm9g16udk1826wmr5f4x.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%2Flm9g16udk1826wmr5f4x.png" alt="Lost strays on one street: complaints the city recorded without coordinates, listed by street and cross street instead of being placed on the map" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwhcau4b7m18szu4i6nc.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%2Fxwhcau4b7m18szu4i6nc.png" alt="The ghost hall: cards for pets whose reported pothole the city inspected and did not find" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frlg534os0mato5vupsk6.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%2Frlg534os0mato5vupsk6.png" alt="The adoption form for a shelter pet: a name and a short note, which stays private until a person approves it" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw1sy4uiapxvmqc5cmcsx.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%2Fw1sy4uiapxvmqc5cmcsx.png" alt="The sync log: the last successful sync time and a table of recent runs with fetched, new, changed and status-event counts" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkkp4a2ozq3jdkgqzld9x.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%2Fkkp4a2ozq3jdkgqzld9x.png" alt="The public read-only mirror of the Shelter Office: moderation and review queues, live workflow instances by stage, sync health and the lifecycle board" width="800" height="991"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgd69nqfljpseqh4xq86p.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%2Fgd69nqfljpseqh4xq86p.png" alt="The gallery on a phone: filters and pet cards in a single column" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw5hxr95msk67ko6l2ub1.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%2Fw5hxr95msk67ko6l2ub1.png" alt="A pet's page on a phone: its bio, badges and rounded map pin" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing the Shelter Office (App SDK, needs a Sanity login):&lt;/strong&gt; there's no shared test account. The video shows a note being approved in the Shelter Office at 2:18, and every Office view has a public read-only mirror at &lt;a href="https://pothole-shelter.vercel.app/office" rel="noopener noreferrer"&gt;/office&lt;/a&gt;.&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/syncaimain" rel="noopener noreferrer"&gt;
        syncaimain
      &lt;/a&gt; / &lt;a href="https://github.com/syncaimain/pothole-shelter" rel="noopener noreferrer"&gt;
        pothole-shelter
      &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;Pothole Adoption Agency&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/syncaimain/pothole-shelter/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/syncaimain/pothole-shelter/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/syncaimain/pothole-shelter/actions/workflows/sync.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/syncaimain/pothole-shelter/actions/workflows/sync.yml/badge.svg" alt="311 sync"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Real NYC 311 pothole complaints from Queens Community Board 13, turned into adoptable pets whose fates follow the city's
own record: repaired, "no pothole found", transferred, or still waiting. Built for the Sanity Challenge on DEV, Path Two.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live site:&lt;/strong&gt; &lt;a href="https://pothole-shelter.vercel.app" rel="nofollow noopener noreferrer"&gt;https://pothole-shelter.vercel.app&lt;/a&gt; (no login)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanity project ID:&lt;/strong&gt; &lt;code&gt;fixjy07h&lt;/code&gt;, dataset &lt;code&gt;production&lt;/code&gt;, public-read. Example queries are in &lt;a href="https://github.com/syncaimain/pothole-shelter/docs/links/public-queries.txt" rel="noopener noreferrer"&gt;docs/links/public-queries.txt&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build log&lt;/strong&gt; (the honest version): &lt;a href="https://github.com/syncaimain/pothole-shelter/docs/BUILD_LOG.md" rel="noopener noreferrer"&gt;docs/BUILD_LOG.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What a judge can check&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Open the gallery and click a Feral pet.&lt;/li&gt;
&lt;li&gt;Read its complaint history and follow a link to the real 311 record on NYC Open Data.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;/strays&lt;/code&gt; (complaints with no coordinates) and &lt;code&gt;/ghosts&lt;/code&gt; (inspected, no defect found).&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;/sync&lt;/code&gt; for the last sync time, the run log, and the unmapped resolution phrases.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Numbers (live dataset, 4 October 2026)&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;


&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complaints synced&lt;/td&gt;
&lt;td&gt;2,512 (every Queens CB 13 street pothole from the last 180 days, plus every one still open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lost&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/syncaimain/pothole-shelter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A pnpm workspace: &lt;code&gt;studio/&lt;/code&gt; (schema and desk structure), &lt;code&gt;web/&lt;/code&gt; (Next.js 16 public site), &lt;code&gt;sync/&lt;/code&gt; (311 → Sanity, with the&lt;br&gt;
outcome rules and clustering), &lt;code&gt;workflows/&lt;/code&gt; (definitions and bench tests), &lt;code&gt;app/&lt;/code&gt; (the App SDK Shelter Office), &lt;code&gt;agent/&lt;/code&gt;&lt;br&gt;
(Gemini bio writer and fact guard) and &lt;code&gt;ingest/&lt;/code&gt; (discovery, migrations, and every raw 311 snapshot with its SHA-256).&lt;br&gt;
CI runs typecheck, lint, all tests, a snapshot checksum check, both builds, and the Playwright judge path with axe-core.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;IDE and agent:&lt;/strong&gt; I built this with &lt;strong&gt;Claude Code&lt;/strong&gt; (Claude Opus 5.5) in a terminal, driven by prompts and a written spec.&lt;br&gt;
A second coordinating session sent guidance through &lt;code&gt;docs/GUIDANCE.md&lt;/code&gt;, and the building agent reported back in&lt;br&gt;
&lt;code&gt;docs/REPORT.md&lt;/code&gt;. Both files are in the repo, unedited, along with the full log in &lt;code&gt;docs/BUILD_LOG.md&lt;/code&gt;. The app itself&lt;br&gt;
uses &lt;strong&gt;Gemini&lt;/strong&gt; (&lt;code&gt;gemini-3.7-flash&lt;/code&gt;) for bios.&lt;/p&gt;

&lt;h3&gt;
  
  
  The prompt that set the tone
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Read the spec in full. Then, in order: verify the Gemini key with one cheap call (if it fails, stop and report); confirm&lt;br&gt;
every item marked "Verify" against current live docs; scaffold the pnpm workspace. Stop there and report before writing&lt;br&gt;
feature code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Stop and report" was the most useful instruction I gave. Several of the spec's own facts were wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Workflows package names were partly wrong.&lt;/strong&gt; The CLI binary is &lt;code&gt;sanity-workflows&lt;/code&gt;, it needs
&lt;code&gt;@sanity/workflow-blueprint&lt;/code&gt;, and the test bench lives in &lt;code&gt;@sanity/workflow-engine-test&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript had to be pinned to 6.0.3,&lt;/strong&gt; not the 7.0.2 that &lt;code&gt;latest&lt;/code&gt; gave, nor the 5.x one guide suggested. It's the only
version that satisfies both &lt;code&gt;typescript-eslint&lt;/code&gt; (&amp;lt;6.1) and the Workflows blueprint (≥6.0.3).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16 ships an &lt;code&gt;AGENTS.md&lt;/code&gt; that says "This is NOT the Next.js you know"&lt;/strong&gt; — and it isn't. Caching is now
&lt;code&gt;cacheComponents&lt;/code&gt; + &lt;code&gt;'use cache'&lt;/code&gt;. The first build failed because "Feral of the week" read the clock outside a cached scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel's Hobby plan only allows daily crons,&lt;/strong&gt; so the sync is scheduled hourly on GitHub Actions instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Messy data, decided honestly
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; Queens had ~17,000 street-pothole complaints in 180 days, plus ~8,800 still open. Rather than sample, I took
&lt;strong&gt;one community board in full&lt;/strong&gt;, so every pet is traceable and clustering sees complete data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing coordinates:&lt;/strong&gt; 62% of complaints have no point. I chose not to geocode them, because that would invent
precision the record doesn't have. They became the lost strays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Referred" resolutions&lt;/strong&gt; never sit on a closed ticket in CB 13; they're all Pending. The status rule stays in charge
rather than a mapping overriding a ticket the city still lists as open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unclear resolution text:&lt;/strong&gt; "The status of this Service Request is currently not available online…" is the whole
Unmapped bucket. It's shown publicly rather than guessed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The median time to close is misleading.&lt;/strong&gt; It's 0.4 days overall because 366 duplicates closed within one minute of
being filed. I quote the repair median (0.9 days) instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;91% of located complaints have no &lt;code&gt;street_name&lt;/code&gt;&lt;/strong&gt;: they're intersections, with only &lt;code&gt;intersection_street_1/2&lt;/code&gt;. A
clustering rule keyed on &lt;code&gt;street_name&lt;/code&gt; would silently have skipped them. A test caught it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"0 m apart" means "the same corner",&lt;/strong&gt; not "the same pothole": intersection complaints all share the intersection's
point. The Shelter Office tells reviewers so.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where it got stuck, and how it course-corrected
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dotted document IDs.&lt;/strong&gt; The spec's own example (&lt;code&gt;complaint311.&amp;lt;key&amp;gt;&lt;/code&gt;) would have hidden every document from judges,
because Sanity never serves dotted IDs to anonymous readers. I switched to hyphens. Later I used the &lt;em&gt;same&lt;/em&gt; behaviour on
purpose: pending adoption notes get a dotted ID, so nothing a visitor types is public before a person approves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One document per status event put me over the plan's cap.&lt;/strong&gt; 4,992 event documents took the dataset to 10,022 of
10,000. Events now live as an append-only array inside each pet. I migrated by &lt;strong&gt;moving&lt;/strong&gt; every event verbatim,
verified 4,992/4,992 in place, and only then deleted. The dataset fell to about 5,000 documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;$actor.kind == "person"&lt;/code&gt; gated nothing.&lt;/strong&gt; The tests said people could approve, but they also said agents could. A
probe showed the Workflows engine stamps &lt;em&gt;every&lt;/em&gt; resolved actor as kind "person", robots included. What actually
discriminates is the id namespace (&lt;code&gt;g…&lt;/code&gt; user, &lt;code&gt;p-…&lt;/code&gt; robot token). The bug stayed hidden because my test fixtures used
fake ids that happened to look like robots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I said "nothing was written" when something was.&lt;/strong&gt; A migration step was interrupted, the tool reported the call as
rejected, and the agent told me nothing had been appended. It had. The agent found this itself on the next run and
corrected the record. Lesson: check the data before stating what didn't happen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stopping a shell doesn't stop its process.&lt;/strong&gt; Two long backfills were cut off at a time limit, but their processes kept
running and raced the re-runs, creating 183 duplicate workflow instances. The agent traced them by timestamp, added an
optimistic-lock claim (&lt;code&gt;ifRevisionID&lt;/code&gt;) so it can't recur, and &lt;strong&gt;aborted&lt;/strong&gt; the duplicates through the engine with a
recorded reason rather than deleting them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three different pets were all called "Arepa Asphaltine".&lt;/strong&gt; 800 base names for 2,512 pets. This was found by
&lt;em&gt;reading&lt;/em&gt; the first Gemini bios instead of trusting that they had passed the guard. Pets sharing a base name now take
pedigree ordinals (II, III…) in complaint-key order, and a newer pet never renames an older one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;axe-core found a real accessibility bug:&lt;/strong&gt; on phones the sync tables scroll sideways, but the scroll region wasn't
keyboard-focusable. Fixed, and now tested on every push.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reaching past the Studio: Workflows and the App SDK
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Workflows.&lt;/strong&gt; The spec asked for a 2-hour spike with a declared fallback. The spike &lt;strong&gt;succeeded&lt;/strong&gt;, and nothing fell back.&lt;br&gt;
Three definitions were validated offline and tested on the in-memory bench, including the 60-day Feral rule on a fixed&lt;br&gt;
clock: still in the shelter one second before 60 days, feral at exactly 60. They're deployed from a &lt;code&gt;sanity login&lt;/code&gt; session.&lt;br&gt;
A project-scoped token deploys fine and then fails on a cross-resource read, exactly as the docs warn.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pothole-lifecycle&lt;/code&gt;: Reported → In the shelter → Adopted · Ghost · Transferred · Feral · &lt;strong&gt;Unmapped&lt;/strong&gt;. I added the
Unmapped stage; without it, a closed-but-unmapped pet would wrongly turn feral by the clock. The sync (a robot token) is
the only caller that can record an outcome, and it ticks the clock-based transitions itself. There's one live instance
per open pet: 981 right now, matching the pets' outcomes exactly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cluster-review&lt;/code&gt;: Proposed → Approved · Rejected. The rule proposes, and a person decides in the Shelter Office. The sync
merges only clusters whose decider is a person.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adoption-moderation&lt;/code&gt;: Submitted → Approved · Rejected, decided by a person.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two lessons the docs didn't spell out. Engine gates are &lt;strong&gt;advisory&lt;/strong&gt;: the actor is "provenance, not an authenticated&lt;br&gt;
principal", and mutation guards aren't enforced by the lake yet. So the real rules also live in code I control. And&lt;br&gt;
instance documents are &lt;strong&gt;private&lt;/strong&gt; (dotted IDs), so the public mirror reads workflow state through a server-side proxy that&lt;br&gt;
publishes totals only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App SDK.&lt;/strong&gt; The &lt;strong&gt;Shelter Office&lt;/strong&gt; is a custom app in the Sanity Dashboard, with real-time data and its own interface: live&lt;br&gt;
queues to moderate adoption notes and review cluster merges, the unmapped phrases, sync health, and a live lifecycle board.&lt;br&gt;
Approving a note fires the moderation workflow &lt;em&gt;as the signed-in person&lt;/em&gt;. The engine records who decided from their&lt;br&gt;
token, and only then is a public copy of the note written. Every Office view has a public read-only mirror at&lt;br&gt;
&lt;a href="https://pothole-shelter.vercel.app/office" rel="noopener noreferrer"&gt;/office&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini, behind a fact guard.&lt;/strong&gt; &lt;code&gt;gemini-3.7-flash&lt;/code&gt; (version &lt;code&gt;3.7-flash-08-2026&lt;/code&gt;, recorded on every bio it writes)&lt;br&gt;
restyles template bios into the shelter's voice. The guard accepted 65 of 65 in the sample run. Reasoning models eat&lt;br&gt;
output tokens: a two-letter test reply used 80 reasoning tokens, so bios get a 4,096-token budget.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I cut
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All of Queens.&lt;/strong&gt; One community board in full beat a sample of the whole borough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geocoding strays.&lt;/strong&gt; It would have put invented points on a map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filing new complaints.&lt;/strong&gt; Out of scope by design: this shelter only reflects the city's record.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Known limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resolution wording is inconsistent,&lt;/strong&gt; and five closed complaints say nothing useful. They stay Unmapped until a person decides.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clustering is a heuristic&lt;/strong&gt; (60 m / 30 days / same street; strays by same block). That's why every merge needs a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;311 dates have no time zone.&lt;/strong&gt; I read them as New York local time. The data is consistent with that, but it's an assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hourly schedule is best-effort.&lt;/strong&gt; GitHub Actions delays scheduled runs under load and sometimes skips them: on
4 October the 19:17 UTC slot ran 22 minutes late and the 20:17 slot didn't run. Every run, scheduled or manual, is
idempotent, and the &lt;a href="https://pothole-shelter.vercel.app/sync" rel="noopener noreferrer"&gt;sync log&lt;/a&gt; shows real run times, never the schedule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why it's new
&lt;/h3&gt;

&lt;p&gt;Most civic-data projects turn records into dashboards. This one turns them into characters, and lets the data's gaps play&lt;br&gt;
the parts: the complaints with no coordinates become lost strays, the ones the city looked for and couldn't find become&lt;br&gt;
ghosts, and the backlog becomes a feral wing that's still waiting. Every fate is the city's own, and every step can be&lt;br&gt;
traced back to a real record.&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;fixjy07h&lt;/code&gt; · &lt;strong&gt;dataset:&lt;/strong&gt; &lt;code&gt;production&lt;/code&gt; (public-read, no token needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public query:&lt;/strong&gt; &lt;a href="https://fixjy07h.api.sanity.io/v2025-02-19/data/query/production?query=*%5B_type==%22pothole%22%5D%5B0...5%5D" rel="noopener noreferrer"&gt;https://fixjy07h.api.sanity.io/v2025-02-19/data/query/production?query=*[_type=="pothole"][0...5]&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Schema, and why raw records and pet fields are kept apart:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;complaint311&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The 311 row &lt;strong&gt;exactly as published&lt;/strong&gt; (&lt;code&gt;raw&lt;/code&gt;), its SHA-256, and the source row URL. Rewritten only when the hash changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pothole&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The pet: name, temperament, bio (with its model and fact-guard provenance), rounded location, outcome (written only by the sync), its lifecycle workflow instance, and &lt;code&gt;events&lt;/code&gt;, an append-only history where every entry cites the complaint field that caused it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolutionMapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One exact city phrase → outcome, with a person's rationale.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clusterDecision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A proposed merge, with its review workflow and the person who decided.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;adoption&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A visitor's note: private while pending, public only once a person approves it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syncRun&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every sync: counts, watermark, and the SHA-256 of each raw page it saved.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keeping the city's record and my interpretation in separate documents means anyone can check any pet against the 311 row&lt;br&gt;
it came from, and a re-sync can never overwrite a decision a person made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data:&lt;/strong&gt; &lt;a href="https://data.cityofnewyork.us/Social-Services/311-Service-Requests-from-2020-to-Present/erm2-nwe9" rel="noopener noreferrer"&gt;311 Service Requests from 2020 to Present&lt;/a&gt;,&lt;br&gt;
NYC Open Data, dataset &lt;code&gt;erm2-nwe9&lt;/code&gt;. The City of New York does not vouch for the accuracy or completeness of this site or&lt;br&gt;
the data it uses, which has been modified for use from its original source.&lt;br&gt;
[CHECK BEFORE PUBLISHING: confirm this disclaimer wording against the current NYC Open Data terms in a browser]&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Session
&lt;/h2&gt;

&lt;p&gt;[AGENT SESSION: upload the Claude Code transcript at &lt;a href="https://dev.to/agent_sessions/new"&gt;https://dev.to/agent_sessions/new&lt;/a&gt;, slice the parts worth showing&lt;br&gt;
(suggested: the "Verify" pass, the document-cap migration, the &lt;code&gt;$actor.kind&lt;/code&gt; probe, the "nothing was written"&lt;br&gt;
retraction), set it to &lt;strong&gt;Make Public&lt;/strong&gt;, and embed it here. Scan it for keys first: token values were only ever read from&lt;br&gt;
&lt;code&gt;.env.local&lt;/code&gt; inside commands, never printed, but check anyway.]&lt;/p&gt;

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