This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange
What I Built
Cryptonym Desk gives you a spy file for your agent. You type in films, anime and characters you like. It gives you back three names: a CIA-style cryptonym (a real two-letter office digraph plus a word that means nothing, like AEDINOSAUR or LIENVOY), a working alias made by grafting your names together at their vowels, and an ADJECTIVE NOUN field codename.
The first version was one 29 KB HTML file with every word list hard-coded in a <script> tag. For this challenge I moved the lists into a public Sanity dataset and rebuilt the page in Astro, which reads that dataset when the site is built.
It's for people who name things: agents, bots, side projects, D&D characters. It doesn't take itself seriously. The one thing I kept strict is that nothing you type ever leaves the page.
Demo
- Live: https://casareanderson.github.io/cryptonym-desk-sanity/
- The corpus, rendered from Sanity: https://casareanderson.github.io/cryptonym-desk-sanity/corpus/
Code
https://github.com/casareanderson/cryptonym-desk-sanity (MIT). The original single-file version is at https://github.com/casareanderson/cryptonym-desk.
My Build Process
The tool: Claude Code, in a terminal, not an IDE. I gave it a short brief and it did the build end to end: import, schema, Astro port, tests, GitHub Action, deploy. I reviewed the result. What went wrong along the way is more useful than what went right, so this section is mostly that.
1. The schema came first, and the data got cleaner as a result. The three document types are digraph (code, provenance note, retired flag), wordBank (bank, value, weight) and preset (label, seed list). The corpus came to 103 documents. Just turning the arrays into documents exposed a bug: MERIDIAN was in the noun list twice, so it had been drawn twice as often as any other noun. In a JavaScript array you never notice that. As separate documents, it stands out immediately.
2. A test written from the README failed, and the README was the thing that was wrong. The README says the splitter cuts Spiegel into Spie·gel and Kusanagi into Ku·sa·na·gi, so Spiegel × Kusanagi gives Spienagi. The agent wrote a test for exactly that. It failed. The regex had always kept one consonant after each vowel group, giving Spieg·el and Kus·an·ag·i, so across 200 seeds that pair only ever produced Spiegagi. The README's own example could not happen. The obvious fix was to change the test to match the code. Instead we treated the documented behaviour as the design and the regex as the bug. The code now matches the README, and there's a test that the README's example can actually be produced. The cost: the same inputs now give different aliases than the original desk did.
3. The weight field was decoration at first. The schema had it and the generator did weighted picks, but every entry weighed 1, so none of it did anything. I only noticed when the corpus page showed a column of ×1. The fix was an edit in the dataset, not the code: plain SECRET weighs 5 and CODE WORD weighs 0.5. That edit is also the end-to-end proof. The corpus revision printed on the live site went from 211eb8a to 4ca7fee on the next build, with no code change.
4. Every record now carries the corpus revision. Records have always been seeded (same inputs plus the same salt gives the same record), which is what makes a "file reference" mean something. Once the words live in a dataset, the same inputs can produce a different record after someone edits a bank. So the page hashes every document's _rev into a 7-character revision and prints it on the record and in "Copy record". Without it, "same inputs, same record" would quietly stop being true.
5. The build refuses a corpus that would break the page. If a bank is empty, a bank name is unknown, a weight is zero or no digraphs are active, astro build fails, so the page can't ship and then throw on click. The Studio schema enforces the same rules earlier (positive weights, uppercase 2–3 letter codes, at least two seeds per preset).
6. What I deliberately didn't do. The corpus is fetched at build time only. The page makes no runtime calls to Sanity, because it has always promised that nothing you type leaves the browser, and I'd rather keep that promise than add a live query. Edits reach the site through a GitHub Action that runs on push, on demand and nightly. A Sanity webhook would be faster, but it would mean storing a GitHub token inside Sanity, and a nightly rebuild is plenty for a word list.
7. Where it stops. This is a read-only frontend. I didn't reach past the Studio into the App SDK or Workflows, which the challenge says it especially wants to see. If I extend it, the obvious next step is a Workflow that lets people propose words, with a person approving each one into a bank. The Studio is deployed (cryptonym-desk.sanity.studio), but it needs project membership, so the public corpus page is how you inspect the data. One honest gap in the data: each digraph's provenance note is a generic "real digraph from declassified material" line, not an individual citation.
Small things that went wrong: The Studio's first sanity deploy stopped and asked for the new app ID to be pinned in sanity.cli.js. The headless screenshot step first tried a Python Playwright that wasn't installed, then used the Node one. And GitHub Actions now warns that Node 20 actions are deprecated.
Tests: 9 node:test cases. They cover determinism, the revision on each record, weighted-pick distribution (10,000 draws), split parts rejoining to the original word, the README example actually appearing, and the corpus validator rejecting each kind of bad data.
Sanity Project Details
-
Project ID:
en9phc0q, datasetproduction(public read) - Read the whole corpus, no token needed: https://en9phc0q.api.sanity.io/v2024-01-01/data/query/production?query=*%5B_type%20in%20%5B%22digraph%22%2C%22wordBank%22%2C%22preset%22%5D%5D
-
Schema:
studio/schemaTypes/index.js - The GROQ query the page is built from is printed at the bottom of the corpus page.
Agent Session
Not embedded. The session that built this ran on my homelab box, and the transcript is full of private network detail that has nothing to do with the build. I'd rather leave it out than publish a heavily redacted version. The repo's commit history, tests and this write-up are the record.
🤖 Built with an AI coding agent (Claude Code) and drafted with AI assistance from the build notes, commits and test output, then reviewed before publishing.


Top comments (0)