DEV Community

Hashir
Hashir

Posted on

GrantQuest — I Turned Scholarship Hunting into an RPG

Sanity Challenge Path Two Submission

 This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange
What I Built
GrantQuest — scholarship hunting, but it's an RPG quest log.
Every scholarship is a quest. Eligibility requirements are gates you clear one by one. Documents are inventory you gather. Deadlines are countdown timers that do not care about your feelings. Clearing gates earns XP, and your Quest Log tracks every application like a campaign journal.
But here's the twist that makes it more than a theme: scholarship data rots. Deadlines change, programs get discontinued, amounts get updated every year — and half the scholarship sites on the internet are quietly serving you last year's facts. So every quest in GrantQuest carries a freshness badge: FRESH (verified within 30 days and the deadline still ahead) or NEEDS RE-VERIFICATION. The data's trustworthiness is modeled as data, not vibes.
25 real scholarships. Real deadlines. Real money. No login, no signup — just open it and start your expedition.
It's for students like me who are drowning in 40 open tabs trying to figure out if they're eligible for anything.
Demo
Play it here: https://grantquest-ruddy.vercel.app
Video walkthrough (60 seconds):

There's also a 30-second guided tour built right into the site — hit "Take the 30-second tour" on the hero and it walks you through the whole loop.
Code
https://github.com/mateir0/grandquest
My Build Process
I built this with OpenCode running Muse Spark, one prompt at a time, fifteen prompts total. The plan was simple: quest board, quest detail pages, an eligibility engine, a quest log with XP, and a freshness-verification workflow in the Studio. Then the stretch goals: derived freshness badges, the official Workflows API, a deadline-sweeper Function, an App SDK board, and a guided tour for judges.
Here's where it went wrong, because that's the interesting part:
The deploy that 404'd everything. My Next.js app imported shared logic from a folder above web/. Worked perfectly on my machine. Vercel 404'd the entire site, because Vercel only uploads the web/ directory and my import pointed at files that didn't exist up there. Lesson now burned into my brain: never import above web/ in a Next.js app on Vercel. I vendored the file into web/lib/, redeployed, done.
The freshness rule I overcomplicated. My first version said a quest goes stale if its deadline is within 60 days. Then I stared at it and realized: that's the countdown's job, not the freshness badge's job. Freshness answers "is this data still true?" Urgency answers "is the deadline close?" Two different questions, two different UI elements. Simplified the rule to: verified within 30 days AND deadline not passed = FRESH. Everything else gets flagged. The code got shorter and the concept got sharper, which is usually a sign you're done.
The ERSU incident — my favorite failure. I had a test record for an Italian scholarship (ERSU Messina) sitting in the dataset with half-guessed data. The lazy move would've been stamping it verified and moving on — who's going to check, right? Instead I went and read the actual official bando, in Italian, from ersumessina.it, and replaced every guessed field with real numbers: real amounts (€7,171.11 for fuori sede), real ISEE thresholds, real dates. And it now correctly shows NEEDS RE-VERIFICATION, because applications closed back in August. An honest red badge beats a lying green one. That record is the whole philosophy of the app in one card.
The Vanier plot twist. While researching 10 new scholarships to add before submission, I discovered that the Vanier Canada Graduate Scholarship — one of the most famous scholarships on the planet — is officially discontinued. "No longer accepting applications," right there on the government website. So I replaced it with its actual successor, the Canada Graduate Research Scholarship–Doctoral. The freshness system exists because of exactly this kind of thing: the ground moves under you and nobody tells you.
The janky tour. First version of the guided judge tour felt laggy when I watched it back. You know the feeling — something's just slightly off and you can't unsee it. Rebuilt the animation approach until it was smooth.
The leaked token. My final security sweep found a real Sanity write token sitting in a committed .env.example. Sanitized it to a placeholder, revoked the token, rotated a fresh one. (The old one is still in git history, but it's dead, so it doesn't matter.)
Where I reached past the Studio:
Workflows (official API): every quest moves unverified → underReview → verified through the real Workflows engine — not a status dropdown, actual workflow transitions. All 25 quests were driven through real transitions.
Functions: a scheduled Function sweeps daily. Any quest whose deadline passed, or whose verification is older than 30 days, gets flagged automatically — with an immutable audit log of every sweep.
App SDK: the Quest Master's Board, a dashboard app for working through the verification queue.
Sanity Project Details
Project ID: aitdwcxh · Dataset: production
Public dataset — query it yourself:
https://aitdwcxh.api.sanity.io/v2024-01-01/data/query/production?query=*%5B_type%3D%3D%22quest%22%5D
Schema highlights: quests with structured eligibility gates (not prose — actual typed data with requirements), documents as inventory items, deadlines, award amounts, and the verification workflow state machine. Gates being data instead of text is the whole trick — it's what lets the app compute your eligibility instead of just displaying a wall of text.

Top comments (0)