This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange
What I Built
I have a folder of side projects that stopped at some point and never really came back. Some of them were useful for a while. Some made it into production. Some survived for two days, got a README, and then spent the next three years pretending they were merely "paused".
So instead of deleting them, I built them a cemetery. 😂😂😂
Each project gets a stone: its years, an epitaph, a cause of death, the stack, the last commit message, and the one thing it taught me. You can walk the yard, hover a cause of death to see which graves it took, read an obituary, and light a candle.
No stone has its own artwork. A stone's height comes from how long the project lived, its moss and cracks from how long ago it died, its marks from the cause of death, its brass and flowers from being retired with honour. A two-day todo app is a low marker with a wooden tag. A framework that never had a user is a mausoleum.
The home page opens with a register: 14 buried, 3 retired with honour, average lifespan 355 days, most common cause of death "Lost interest". Nobody typed those numbers. A grave points to its cause and its stack as Sanity documents, and Astro counts them during the build.
There are 18 graves. Five are public repos of mine, written up from their GitHub metadata and READMEs. The other 13 are invented, and you have met them before: the todo app that lived two days, the framework with a router, an ORM and no users, the newsletter with one subscriber and a 100% open rate.
Demo
Live site: side-project-graveyard.boyko-nazar.workers.dev
Three clicks worth making:
- The register on the home page, then hover "Lost interest" in the legend and watch the other graves step back.
- Sunday Letter, the newsletter tool. Read the last line of the obituary.
- The candle on any grave. It counts in your browser and nowhere else, and the page says so. Go back to the yard afterwards: that grave is lit there too.
The animated tour at the top is a Playwright recording of the live site, converted to a GIF with ffmpeg.
Code
Side Project Graveyard
Every repo deserves a proper burial.
I have a folder of side projects that stopped. This site gives each of them a stone: an epitaph, a cause of death, the stack it was built with, its last commit message, and the one thing it taught me. The content lives in Sanity, the site is Astro, and I built it for the DEV × Sanity Challenge by prompting Claude Code.
- Live site: https://side-project-graveyard.boyko-nazar.workers.dev
- Studio: https://side-project-graveyard.sanity.studio
- Sanity project ID
20pcz8by, datasetproduction(public) - Build log, with every prompt and every failure: docs/BUILD_LOG.md
All six screenshots, at 1440, 768 and 390 pixels, are in docs/screenshots.
Content model
Four document types, defined in studio/schemaTypes/.
| Type | What it is | Fields |
|---|---|---|
project |
A grave | name, slug, epitaph, status (buried, retired, undead), born and died dates, cause, stack, last commit, mood at death, lines of code, obituary, lesson, repository, and an |
-
studio/is the Sanity Studio: four schema types, the desk structure, the seed content, the script that set the monument fields. -
web/is the Astro site: one file of GROQ queries, one file that turns a record into a monument, one of date and stats maths, all tested; five page templates; one stylesheet bundle; every drawing hand-written SVG; five generated images for the scene and the stone, inweb/src/assets/scene/. -
docs/DESIGN_BRIEF.mdis the redesign plan, written from two outside reviews. -
docs/visual-enhancement/is the visual audit a second agent wrote before it touched the materials, anddocs/image-prompts.mdhas the prompts for the five images. -
docs/BUILD_LOG.mdis the prompt log. Every prompt is in it word for word, and so is every failure. -
CLAUDE.mdholds the rules the session ran under.
My Build Process
I used two tools. Claude Code built the site and the redesign, in one session. Codex, a second agent, wrote a visual audit and implemented the material pass at the end. Claude Code then reviewed that pass and corrected it.
Before the session I wrote two things by hand. A plan: the concept, the schema, and one prompt for each of eight phases. And a rules file: one phase is one branch and one pull request, merge when CI is green, write every prompt and every failure into the build log, stop only when a step needs my browser. Then I typed one kickoff prompt and the model read the phase prompts from the plan.
The numbers: 16 pull requests, and CI never failed on one of them. The build took 8 phases and about 95 minutes of the model's work; the first five phases, from a bare Studio scaffold to 42 built pages, took 52. Then the session waited seven hours for me to click through a hosting dashboard. The redesign took five more pull requests and about 65 minutes, with two stops for my review. The material pass was one pull request by the second agent, and the review of it took Claude Code 66 minutes. A final check before publishing found twelve dependencies without a credit in the README; that fix was the sixteenth pull request. 25 messages from me in all.
The parts worth telling are the ones where the prompt and the result disagreed.
My kickoff was wrong in three places. It said my global git excludes would hide the tooling folder. They did not. It assumed a git identity existed on the machine. None did. It asked for a job-level if: hashFiles(...) in the CI workflow, which GitHub Actions does not allow. The model caught all three before anything failed: it ran git check-ignore before staging, looked up the author on my earlier public repos before the first commit, and moved the condition to the steps. The build log lists them under "what failed", because my prompt failed, even if no command did.
The schema got its real test from a broken file. The prompt asked for a date rule: a death date is required unless the project is undead, and it cannot come before the birth date. The model wrote 17 validation rules with messages in the voice of the graveyard keeper. Then it could not check them on screen, because the Studio stops at a login and it does not sign in to my accounts. So it wrote three broken graves into a file and ran sanity documents validate on it. Seven errors came back, among them "A project cannot die before it is born." and "Only the undead may go without a date of death."
astro add installed React into a site with a no-React rule. npx astro add @sanity/astro added react, react-dom, sanity and styled-components as direct dependencies. They are peers of the integration, for an embedded Studio this site does not have. The model removed them and tried to keep them off disk with legacy-peer-deps. That broke astro check, which then printed a misleading request to install @astrojs/check, a package that was already there. The cause was a pruned peer of an unrelated package. The model found it by importing the package by hand, dropped the setting, and let npm install the peers. Same phase: npm installed TypeScript 7, astro check wants 6. Three retries in 13 minutes, all in the log.
The screenshots looked right and the page was wrong. After building the pages the model took screenshots at three widths and they were fine. Then it read the text of the links in the browser and found "Previous graveEverything.js" and "Lost interest5graves". Two spans sat side by side with no space between them, so a screen reader would have read them as one word. No screenshot shows that. Lighthouse came out at 100 in all four categories after the fix, and I trust the number more because of what was found before it.
The plan said Cloudflare Pages. The dashboard gave me a Worker. "Connect to Git" now creates a Worker with static assets, built by Workers Builds. My plan, my rules file and the README the model had drafted all said Pages. I also told the model to continue before the hosting existed and gave it the URL I expected to get. It did not take my word for it. The name did not resolve, there was no .env, sanity hooks list was empty. It deployed the Studio, wrote the webhook test script, and stopped again with that evidence. When I came back with the real URL, the fix was an 11-line wrangler.jsonc and a reworded README. A publish in the Studio now reaches the live site within a minute. I measured it with a script that stamps the footer line and polls the page.
The review changed the order of the graves. The first version listed the newest death first. That put my five real repos in the top rows, with sober one-liners from their READMEs, and pushed the better epitaphs below the fold. The review phase flagged it. The graves are now ordered oldest first, which is how a cemetery grows: the old part is by the gate.
The redesign started from a working site that looked like a dashboard. Five tiles, a row of pills, eighteen identical cards. I had two outside design reviews done, and one of them proposed a map in TypeScript with a visual entry per project. That would have put content in code and broken for the next grave. The brief I wrote instead had one rule: the stone is computed from the record. The model added one field to the cause (motif) and one optional group to the grave (monument: shape, relic, inscription, figures), set them with one script, and wrote describeMonument(project, now), which knows no grave by name. A test runs it on the seed file and checks the brief's own counts: 4 ancient stones, 6 aged, 2 settled, 5 fresh, 1 disturbed.
The model was not allowed to ship anything it had not seen. Every visual branch went through at least three rounds of screenshots at three widths, and the model read every image. 19 drawings were redone because of what it saw. The sky had no warm band. The fence strip read as a barcode. Moss looked like polka dots. The smallest stone floated in the sky above the fence because the candle button was pinned to the ground instead of the stone. A shovel pushed a marker off its tag. Signpost, briefcase and crates were brown on brown ground and disappeared. None of these would have shown in a type check.
A second agent added the photographs, and the first one reviewed its work. The redesign was drawn in CSS and SVG and still looked flat. I generated five images in ChatGPT (image model Sol 5.6, effort set to medium, two candidates per image, and I kept the one with the better detail and the right amount of objects in the frame): a wide dusk cemetery, a portrait version for phones, a limestone tile, a soil tile and a strip of grass. Codex wrote an audit of the site first, then put the photographs behind the gate and the limestone on the stones, in one pull request, and reported Lighthouse accessibility 100. Claude Code reviewed that pull request from its own screenshots. It measured the pixels behind the tagline and got 2.65:1. Lighthouse cannot measure text over an image, so the 100 had not covered it. It also blocked the image to test the fallback and found half of the old fence in the dark, found a pale gate post floating in the sky of every grave page, and found that one of its own relics from the day before had been lying across "lived less than a day" the whole time, in a screenshot it had committed itself. Eleven fixes went in. Firefox would not start for the model on my machine, so I opened the preview in Firefox myself, and the log says the check was mine. The last fix came after that: the relics had always had an ink outline in Safari and none in Chrome, and now they are flat in both.
One of the six repos I had named as real graves turned out to be private. The prompt said to skip it in that case, and the model did. For the five real ones it left "mood at death" and "lines of code" empty. Its note in the log: they would have been guesses.
What I cut and why
- A form where visitors bury their own projects. It needs accounts or moderation, and neither fits a weekend.
- A candle count shared between visitors. It needs a write path, and the dataset is read-only from the site on purpose. The count is local and the page says so.
- The Studio embedded in the Astro site. It needs server output, and this site is static files.
- A library of photographed stones. The brief planned cutouts for every shape. Five generated images replaced it: two for the scene, three as materials. The stone shapes are still CSS masks, the words on them are still HTML, and the relics are still hand-written SVG.
- A backdrop I can change from the Studio. The field exists and works, but one wide image cropped for a phone is not the portrait image, so it stays empty and the two local images are the default.
- A gate that swings open on scroll, leaves, fireflies, a crow. One fog band and one cat stayed.
- Every animation except the candle, the undead grave's light, the fog and a 3-pixel lift on hover.
- The App SDK and Workflows bonus. I wanted one path done well.
What I would do differently
I would check the hosting dashboard before writing the plan. Seven hours of this build were me finding out that a product had been renamed.
I would also give the model a way to see the Studio. It tested the schema well without it, but I am the only one who has looked at the desk.
Sanity Project Details
- Project ID:
20pcz8by - Dataset:
production(public) - Studio: https://side-project-graveyard.sanity.studio
- Schema:
studio/schemaTypes/. Four types:project,cause,tech,siteSettings. The grave has an optional Monument group; the cause has a required mark. - Seed content:
studio/seed/graveyard.ndjson, 41 documents.
The dataset answers without a token. This query returns each cause of death with its number of graves:
https://20pcz8by.api.sanity.io/v2026-09-01/data/query/production?query=*[_type=="cause"]{title,"graves":count(*[_type=="project"%26%26references(^._id)])}|order(graves desc)
The content model is small on purpose. Cause and stack are references, so the site can count them. The death date is validated against the birth date and the status. The epitaph is capped at 120 characters because that is what fits on the stone, the inscription at 28 ("The mason charges by the letter."), the figures at three ("A grave is not a dashboard."). The settings are one document that the Studio will not let you duplicate or delete.
Every part of a stone comes from these fields: height from the dates, weathering from the date of death, marks from the cause's motif, brass from the status, shape, relic and inscription from the optional monument. The cat sits on whichever grave was published last, so it moves when I publish.
Agent Session
The full agent transcript is not attached: it contains my local notes and paths that are not part of the project. Every prompt is in docs/BUILD_LOG.md instead, word for word: one entry per phase, with what came out, what failed, and what the model decided on its own. The material pass ran in a Codex session, and its prompts are in the same log.
Seed content is a mix of five real dead projects of mine and 13 invented ones. The stats are computed from all of them at build time.









Top comments (0)