<?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: Hashir</title>
    <description>The latest articles on DEV Community by Hashir (@offxhashir).</description>
    <link>https://dev.to/offxhashir</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%2F4150525%2F15d9c875-d6a1-4774-94a6-404befd15622.png</url>
      <title>DEV Community: Hashir</title>
      <link>https://dev.to/offxhashir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/offxhashir"/>
    <language>en</language>
    <item>
      <title>I built my best friend a mistake engine for the NUST entry test</title>
      <dc:creator>Hashir</dc:creator>
      <pubDate>Sun, 04 Oct 2026 08:53:26 +0000</pubDate>
      <link>https://dev.to/offxhashir/i-built-my-best-friend-a-mistake-engine-for-the-nust-entry-test-434d</link>
      <guid>https://dev.to/offxhashir/i-built-my-best-friend-a-mistake-engine-for-the-nust-entry-test-434d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My best friend SMS is studying for the NUST entry test — the exam that decides whether he gets into Pakistan's top engineering university. For months he cycled through quiz apps, and they all had the same flaw: they'd generate questions, he'd answer them, and the app would forget everything by the next session. Nobody remembered what he got wrong. So he kept losing the same marks twice.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;NEVERTWICE&lt;/strong&gt; to fix exactly that. It's a study app with a memory. The loop is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mistakes → weakness graph → targeted drill → graph update&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You answer questions in a drill. Get one wrong and you classify it by topic with one tap. The weakness graph updates — your threats, ranked worst first. The next drill pulls hardest from the top of that graph. Nail the topic next time and the threat shrinks. Four tabs, zero clutter: DRILL | THREATS | PROGRESS | FILES. Built phone-first, because that's where SMS studies.&lt;/p&gt;

&lt;p&gt;There's also a full 200-question, 180-minute mock for exam-day simulation, and a trajectory view so he can watch his accuracy climb across sessions instead of guessing whether he's improving.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Saturday test
&lt;/h2&gt;

&lt;p&gt;I didn't build this in a vacuum. On Saturday, October 3rd, I handed the app to SMS with one rule: if the drills don't feel uncannily personal — if it can't show him things he actually got wrong — we kill the concept.&lt;/p&gt;

&lt;p&gt;It passed. And he broke three things in the process, all fixed the same day:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classifying mistakes was too slow.&lt;/strong&gt; Tapping through categories after every wrong answer killed his flow. Now it's one tap, with a 700ms auto-advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanations referenced answer letters.&lt;/strong&gt; "The correct answer is B" means nothing when the options shuffle. Now the debrief names the correct answer by its content, not its letter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion sensitivity.&lt;/strong&gt; A full motion pass plus &lt;code&gt;prefers-reduced-motion&lt;/code&gt; support, because a study app shouldn't make anyone dizzy.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's what he said after the session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I definitely see myself using your app to prepare for my future NUST entry test cause on this app i can track my progress via a graph and i can also solve questions from the real NUST NET type mock exam allowing me to understand the depthness of exam questions before I actually attempt the real test"&lt;/p&gt;

&lt;p&gt;— SMS, my best friend and the reason this exists&lt;/p&gt;
&lt;/blockquote&gt;

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

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

&lt;p&gt;Try it live — no signup, no paywall, no ads: &lt;strong&gt;&lt;a href="https://nevertwice.vercel.app" rel="noopener noreferrer"&gt;https://nevertwice.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/mateir0/nevertwice" rel="noopener noreferrer"&gt;https://github.com/mateir0/nevertwice&lt;/a&gt;&lt;/strong&gt; — public repo, MIT licensed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Next.js on Vercel, with question generation running server-side through Groq on an open-weight model (&lt;code&gt;openai/gpt-oss-120b&lt;/code&gt;). The API key never touches the client.&lt;/p&gt;

&lt;p&gt;A few things I'm proud of, stated plainly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It works with no internet.&lt;/strong&gt; A 120-question verified bank ships with the app, and a service worker drops it into offline bank mode. Drills run 100% on the bank with zero AI when you're offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your data is yours.&lt;/strong&gt; Mistake history and the weakness graph live in browser storage. Dossier export/import means you back up or move everything yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest fallbacks.&lt;/strong&gt; When AI generation fails or quotas run out, the app says so — "OFFLINE — BANK MODE", "DAILY PRINT QUOTA SPENT — DRILLS UNAFFECTED" — instead of silently degrading into repeats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security taken seriously.&lt;/strong&gt; Prompt-injection guards on the generation taxonomy, prototype-pollution rejection, body-size limits, security headers, server-side rate limits. There's a SECURITY.md in the repo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;74/74 tests green.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honesty note, because it matters: NUST doesn't release official past papers, and the app says so in its own UI. The mock is modeled on the NET format. It doesn't pretend to be leaked papers.&lt;/p&gt;

&lt;p&gt;Built solo over a weekend with AI pair-programming — the AI handled the boilerplate, I handled the judgment calls. The best feature wasn't anything I planned: watching a red threat shrink because you finally nailed a topic three sessions in a row. I only discovered that by watching SMS use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The challenge asks this directly, so here's the honest answer for NEVERTWICE:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it run with no internet?&lt;/strong&gt; Yes — the offline bank mode exists because of this. The 120-question bank and the service worker mean SMS can drill on a bus with no signal. A closed API-only app couldn't do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it keep his data off servers he doesn't control?&lt;/strong&gt; His mistake history — the most personal data in the app, a map of everything he doesn't know — never leaves his browser. Only the generation prompts go to Groq's cloud. If he doesn't trust that, the bank works fully offline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can the model be swapped?&lt;/strong&gt; Yes, and that's the point of building on an open-weight model. The question validator and the fallback logic don't care which model generates the questions. If Groq throttles, the bank carries him. If a better open model appears tomorrow, it's a config change, not a rewrite. No vendor lock-in on the intelligence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it cost him anything?&lt;/strong&gt; Nothing. Free tier for generation, free bank offline, free hosting. A gift for a friend shouldn't come with a subscription.&lt;/p&gt;

&lt;p&gt;Open didn't just make this cheaper to build. It made it &lt;em&gt;his&lt;/em&gt; — inspectable, forkable, exportable, and usable with zero infrastructure. For a study app holding someone's exam future, that ownership isn't a nice-to-have. It's the whole point.&lt;/p&gt;

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

&lt;p&gt;Built with OpenCode and Muse Spark 1.3, one consolidated prompt per task. The full session record — real transcripts, verbatim quotes, no reconstruction — is in the repo: &lt;a href="https://github.com/mateir0/nevertwice/blob/master/agent-session.md" rel="noopener noreferrer"&gt;agent-session.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Five moments tell the story: the drill engine build, the SMS feedback fixes, the mock Groq-budget fix, the security audit, and the structured logging. The drill planner learned to write dossier-voiced reasons like &lt;em&gt;"Quadratic Equations keeps bleeding — 6 misreads in 3 days. 7 questions engineered to punish skimming."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My favorite exchange: during the mock-fix verification, a run hit a 19-minute Groq throttle, and the call was &lt;em&gt;"STOP the live mock verification (kill process 2992) — do NOT chase the 'no 429' criterion."&lt;/em&gt; Knowing when to stop verifying is engineering too.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For SMS: never twice.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI disclosure: built with OpenCode and Muse Spark 1.3; AI assistance was used to prepare this submission.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>GrantQuest — I Turned Scholarship Hunting into an RPG</title>
      <dc:creator>Hashir</dc:creator>
      <pubDate>Thu, 01 Oct 2026 16:09:37 +0000</pubDate>
      <link>https://dev.to/offxhashir/grantquest-i-turned-scholarship-hunting-into-an-rpg-3b4i</link>
      <guid>https://dev.to/offxhashir/grantquest-i-turned-scholarship-hunting-into-an-rpg-3b4i</guid>
      <description>&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%2F2j0zrqlxg7vxts7tzaic.jpg" 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%2F2j0zrqlxg7vxts7tzaic.jpg" alt="GrantQuest — I turned scholarship hunting into an RPG" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;GrantQuest — scholarship hunting, but it's an RPG quest log.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;25 real scholarships. Real deadlines. Real money. No login, no signup — just open it and start your expedition.&lt;/p&gt;

&lt;p&gt;It's for students like me who are drowning in 40 open tabs trying to figure out if they're eligible for anything.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Play it here:&lt;/strong&gt; &lt;a href="https://grantquest.tech" rel="noopener noreferrer"&gt;https://grantquest.tech&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video walkthrough (60 seconds):&lt;/strong&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/3JO1z3NQTP8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




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

&lt;p&gt;&lt;a href="https://github.com/mateir0/grandquest" rel="noopener noreferrer"&gt;https://github.com/mateir0/grandquest&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's where it went wrong, because that's the interesting part:&lt;/p&gt;

&lt;h3&gt;
  
  
  The deploy that 404'd everything
&lt;/h3&gt;

&lt;p&gt;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. I vendored the file into web/lib/, redeployed, done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lesson now burned into my brain: never import above web/ in a Next.js app on Vercel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The freshness rule I overcomplicated
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ERSU incident — my favorite failure
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An honest red badge beats a lying green one. That record is the whole philosophy of the app in one card.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Vanier plot twist
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The janky tour
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The leaked token
&lt;/h3&gt;

&lt;p&gt;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.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent Sessions
&lt;/h2&gt;

&lt;p&gt;Fifteen prompts, every wrong turn included — the unedited build transcripts, exactly as they happened:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mateir0/grandquest/blob/main/agent-session-embed.md" rel="noopener noreferrer"&gt;Read the full build sessions on GitHub&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I reached past the Studio
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Workflows (official API):&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functions:&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App SDK:&lt;/strong&gt; the Quest Master's Board, a dashboard app for working through the verification queue.&lt;/p&gt;




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

&lt;p&gt;Project ID: &lt;code&gt;aitdwcxh&lt;/code&gt; · Dataset: &lt;code&gt;production&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public dataset — query it yourself:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://aitdwcxh.api.sanity.io/v2024-01-01/data/query/production?query=*%5B_type%3D%3D%22quest%22%5D" rel="noopener noreferrer"&gt;https://aitdwcxh.api.sanity.io/v2024-01-01/data/query/production?query=*%5B_type%3D%3D%22quest%22%5D&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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