<?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: 0xErick</title>
    <description>The latest articles on DEV Community by 0xErick (@x88code).</description>
    <link>https://dev.to/x88code</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3835956%2Fa14f7de9-8b61-4e56-9afe-9d058000ddcd.jpeg</url>
      <title>DEV Community: 0xErick</title>
      <link>https://dev.to/x88code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/x88code"/>
    <language>en</language>
    <item>
      <title>Building ContextGuard AI: A Grounded MERN Study Assistant with Flashcards, Mermaid Diagrams, and PDF Uploads</title>
      <dc:creator>0xErick</dc:creator>
      <pubDate>Sat, 21 Mar 2026 15:13:03 +0000</pubDate>
      <link>https://dev.to/x88code/building-contextguard-ai-a-grounded-mern-study-assistant-with-flashcards-mermaid-diagrams-and-15h1</link>
      <guid>https://dev.to/x88code/building-contextguard-ai-a-grounded-mern-study-assistant-with-flashcards-mermaid-diagrams-and-15h1</guid>
      <description>&lt;h1&gt;
  
  
  Building ContextGuard AI: A Grounded MERN Study Assistant with Flashcards, Mermaid Diagrams, and PDF Uploads
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Students deal with a familiar problem: too many notes, too little time, and no easy way to turn raw lecture material into something they can review quickly.&lt;/p&gt;

&lt;p&gt;That was the motivation behind &lt;strong&gt;ContextGuard AI&lt;/strong&gt;, a MERN stack project that takes lecture notes, extracts key concepts, generates evidence-backed flashcards, and builds a Mermaid concept map from the same source material.&lt;/p&gt;

&lt;p&gt;The most important design choice in this project is not just “use AI.” It is &lt;strong&gt;use grounded AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of letting the model freely invent or enrich information, the app is designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;work only from the user’s notes&lt;/li&gt;
&lt;li&gt;refuse weak or insufficient input&lt;/li&gt;
&lt;li&gt;attach evidence to each flashcard&lt;/li&gt;
&lt;li&gt;validate Mermaid output before showing it&lt;/li&gt;
&lt;li&gt;save everything to MongoDB for later use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I’ll walk through the project step by step and explain how the frontend, backend, and database work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the App Does
&lt;/h2&gt;

&lt;p&gt;ContextGuard AI supports this workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user pastes lecture notes or uploads a PDF/text file.&lt;/li&gt;
&lt;li&gt;The frontend extracts the note text.&lt;/li&gt;
&lt;li&gt;The text is sent to the backend.&lt;/li&gt;
&lt;li&gt;The backend generates:

&lt;ul&gt;
&lt;li&gt;exactly 5 flashcards&lt;/li&gt;
&lt;li&gt;a Mermaid &lt;code&gt;graph TD&lt;/code&gt; concept map&lt;/li&gt;
&lt;li&gt;evidence spans showing where each flashcard came from&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The generated study deck is stored in MongoDB.&lt;/li&gt;
&lt;li&gt;The frontend displays the flashcards and diagram in a split workspace.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That gives the user both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a quick recall format for revision&lt;/li&gt;
&lt;li&gt;a visual map for concept understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This project uses a MERN architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MongoDB + Mongoose&lt;/strong&gt; for persistence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; for the backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; for the frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; for the runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; powers the UI styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pdfjs-dist&lt;/strong&gt; extracts text from uploaded PDFs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;react-mermaid2&lt;/strong&gt; renders concept maps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; is optionally used for live generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If no API key is configured, the app falls back to a grounded heuristic generator so the workflow still works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;At a high level, the repository is split into &lt;code&gt;client&lt;/code&gt; and &lt;code&gt;server&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contextguard AI/
|- client/
|  `- src/
|     `- components/
|        |- Workspace.jsx
|        |- Diagramview.jsx
|        `- Loadingskeleton.jsx
|- server/
|  |- models/
|  |  `- StudyDeck.js
|  `- routes/
|     `- ai.js
|- package.json
`- README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend handles user interaction and rendering.&lt;/p&gt;

&lt;p&gt;The backend handles generation, validation, and storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Designing the Data Model
&lt;/h2&gt;

&lt;p&gt;Before building the AI route, I defined the shape of the saved study deck in MongoDB.&lt;/p&gt;

&lt;p&gt;That schema lives in &lt;a href="///Users/ShadowStrike/Desktop/contextguard%20AI/server/models/StudyDeck.js"&gt;server/models/StudyDeck.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The model stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original &lt;code&gt;rawNotes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;generated &lt;code&gt;flashcards&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mermaidCode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mermaid validation metadata&lt;/li&gt;
&lt;li&gt;warning messages&lt;/li&gt;
&lt;li&gt;refusal state&lt;/li&gt;
&lt;li&gt;generation provider/model metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each flashcard also stores an &lt;code&gt;evidence&lt;/code&gt; object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;quote&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startChar&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endChar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That evidence layer is important because it turns a generic AI feature into a &lt;strong&gt;verifiable study tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is the core idea of the schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FlashcardSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startChar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;endChar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;verificationStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grounded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unverified&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means every saved card is traceable to a specific piece of source text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Building a Grounded AI Route
&lt;/h2&gt;

&lt;p&gt;The main backend logic lives in &lt;a href="///Users/ShadowStrike/Desktop/contextguard%20AI/server/routes/ai.js"&gt;server/routes/ai.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The route exposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/ai/generate
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rawNotes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your notes here"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route starts by validating the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawNotes&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rawNotes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rawNotes field is required.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps empty submissions out of the generation pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  The System Prompt
&lt;/h3&gt;

&lt;p&gt;The most important prompt design rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use only the supplied notes. Do not use outside knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The backend prompt enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;grounded generation&lt;/li&gt;
&lt;li&gt;exact JSON output&lt;/li&gt;
&lt;li&gt;exactly 5 flashcards&lt;/li&gt;
&lt;li&gt;verbatim evidence quotes&lt;/li&gt;
&lt;li&gt;refusal behavior when notes are too weak&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns the route into a structured generation endpoint instead of a free-form chatbot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structured Output
&lt;/h3&gt;

&lt;p&gt;The route expects output in this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"generated"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"refused"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refusalReason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flashcards"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"definition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"quote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"startChar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"endChar"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mermaidCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"graph TD ..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That structure matters because it makes the frontend predictable and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Supporting Live AI and a Fallback Generator
&lt;/h2&gt;

&lt;p&gt;One practical problem in hackathon and student apps is API dependency.&lt;/p&gt;

&lt;p&gt;If the OpenAI key is missing or the network call fails, the whole product should not collapse.&lt;/p&gt;

&lt;p&gt;To solve that, the route supports two generation modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Live OpenAI generation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grounded heuristic fallback&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; exists, the route tries OpenAI first.&lt;/p&gt;

&lt;p&gt;If that fails, it falls back to local logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;generationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWithOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanedRawNotes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;generationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFallbackDeck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanedRawNotes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;generationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFallbackDeck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanedRawNotes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the project much more resilient during development and demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Extracting Evidence from Notes
&lt;/h2&gt;

&lt;p&gt;The project does not just create terms and definitions.&lt;/p&gt;

&lt;p&gt;It also attaches evidence using a helper that finds where a quote exists inside the notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createEvidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawNotes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startChar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rawNotes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startChar&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;startChar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endChar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startChar&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small utility, but it unlocks one of the strongest parts of the app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;users can inspect what the flashcard was grounded on&lt;/li&gt;
&lt;li&gt;the system can reject unsupported output&lt;/li&gt;
&lt;li&gt;the saved data becomes much more trustworthy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Validating Mermaid Before Rendering
&lt;/h2&gt;

&lt;p&gt;Mermaid diagrams are a great way to visualize relationships, but AI-generated Mermaid can fail.&lt;/p&gt;

&lt;p&gt;So the backend validates Mermaid output before storing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateMermaidCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mermaidCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;mermaidCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;mermaidCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graph TD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mermaid code must begin with "graph TD".&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That result gets saved in MongoDB as &lt;code&gt;mermaidValidation&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Saving the Generated Deck to MongoDB
&lt;/h2&gt;

&lt;p&gt;Once the generation result is ready, the route creates a &lt;code&gt;StudyDeck&lt;/code&gt; document and saves it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newStudyDeck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StudyDeck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rawNotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cleanedRawNotes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;flashcards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mermaidCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;mermaidValidation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;refusalReason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;promptVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;flashcardCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flashcards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;newStudyDeck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means every generation is stored as a complete record, not just a temporary response.&lt;/p&gt;

&lt;p&gt;That opens the door for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deck history&lt;/li&gt;
&lt;li&gt;re-reviewing past outputs&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;future quiz/repetition features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 7: Building the Frontend Workspace
&lt;/h2&gt;

&lt;p&gt;The main frontend interface lives in &lt;a href="///Users/ShadowStrike/Desktop/contextguard%20AI/client/src/components/Workspace.jsx"&gt;client/src/components/Workspace.jsx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The workspace is split into two sides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;left&lt;/strong&gt;: note input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;right&lt;/strong&gt;: generated output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split makes the workflow easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input on one side&lt;/li&gt;
&lt;li&gt;output on the other&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Local UI State
&lt;/h3&gt;

&lt;p&gt;The component tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rawNotes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flashcards&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mermaidCode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isLoading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isUploading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uploadMessage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;deckMeta&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;activeTab&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the interface responsive while generation happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Adding PDF Upload Support
&lt;/h2&gt;

&lt;p&gt;Instead of only supporting pasted text, the app also supports PDF upload using &lt;code&gt;pdfjs-dist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That logic is inside &lt;code&gt;Workspace.jsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readPdfFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTextContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the app a much more realistic student workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;upload a lecture handout&lt;/li&gt;
&lt;li&gt;extract note text&lt;/li&gt;
&lt;li&gt;generate revision material immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 9: Rendering Output with Tabs
&lt;/h2&gt;

&lt;p&gt;The output panel uses a simple tab model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tabs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flashcards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Flashcards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;diagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Diagram&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why tabs?&lt;/p&gt;

&lt;p&gt;Because the user has two different study modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rapid recall with flashcards&lt;/li&gt;
&lt;li&gt;concept understanding with diagrams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting both into one scroll stream would make the interface harder to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 10: Showing Loading and Refusal States
&lt;/h2&gt;

&lt;p&gt;Good AI UX is not just about the final answer.&lt;/p&gt;

&lt;p&gt;It also needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loading states&lt;/li&gt;
&lt;li&gt;warnings&lt;/li&gt;
&lt;li&gt;refusal messaging&lt;/li&gt;
&lt;li&gt;graceful empty states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project uses a &lt;code&gt;LoadingSkeleton&lt;/code&gt; component while generation runs and displays warnings or refusal messages above the output.&lt;/p&gt;

&lt;p&gt;This makes the app feel much more intentional and reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 11: Handling Mermaid Rendering Errors Gracefully
&lt;/h2&gt;

&lt;p&gt;Mermaid rendering is handled in &lt;a href="///Users/ShadowStrike/Desktop/contextguard%20AI/client/src/components/Diagramview.jsx"&gt;client/src/components/Diagramview.jsx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This component does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;shows a placeholder when there is no diagram&lt;/li&gt;
&lt;li&gt;shows raw Mermaid code if validation failed&lt;/li&gt;
&lt;li&gt;uses an error boundary to catch runtime render failures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The error boundary is especially useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MermaidErrorBoundary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getDerivedStateFromError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means a bad Mermaid string does not crash the app.&lt;/p&gt;

&lt;p&gt;Instead, the user sees a safe fallback and can still inspect the generated code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 12: Responsible AI in the UI
&lt;/h2&gt;

&lt;p&gt;The frontend also includes visible warning copy to reinforce good usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the app is grounded to the user’s notes&lt;/li&gt;
&lt;li&gt;users should verify evidence before treating output as exam-ready&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because responsible AI is not only a backend prompt problem.&lt;/p&gt;

&lt;p&gt;It is also a UX problem.&lt;/p&gt;

&lt;p&gt;Users should be reminded what the system can and cannot guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 13: Environment Variables
&lt;/h2&gt;

&lt;p&gt;The backend is configured through &lt;code&gt;server/.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MONGODB_URI=...
PORT=5000
OPENAI_API_KEY=...
OPENAI_MODEL=gpt-4.1-mini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MongoDB Atlas or local MongoDB&lt;/li&gt;
&lt;li&gt;optional OpenAI usage&lt;/li&gt;
&lt;li&gt;easy environment switching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 14: Running the App
&lt;/h2&gt;

&lt;p&gt;At the root level, the app can be started with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;client
pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;server
pnpm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 15: What Makes This Project Interesting
&lt;/h2&gt;

&lt;p&gt;There are a lot of AI apps that just wrap a chat box around a model.&lt;/p&gt;

&lt;p&gt;This project goes a step further by combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structured output&lt;/li&gt;
&lt;li&gt;verifiable evidence&lt;/li&gt;
&lt;li&gt;visualization&lt;/li&gt;
&lt;li&gt;refusal behavior&lt;/li&gt;
&lt;li&gt;persistent storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes it more useful than a generic summarizer and more trustworthy than a free-form study assistant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges I Ran Into
&lt;/h2&gt;

&lt;p&gt;A few practical challenges came up while building this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI output has to be validated, not trusted&lt;/li&gt;
&lt;li&gt;Mermaid rendering can fail if syntax is malformed&lt;/li&gt;
&lt;li&gt;PDF extraction needs cleanup because raw text is often noisy&lt;/li&gt;
&lt;li&gt;frontend layout can look broken if the Tailwind pipeline is misconfigured&lt;/li&gt;
&lt;li&gt;Vite and environment issues can look like UI bugs at first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the kinds of problems that turn a demo into an actual engineering project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Could Go Next
&lt;/h2&gt;

&lt;p&gt;There are plenty of natural next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deck history and saved sessions&lt;/li&gt;
&lt;li&gt;spaced repetition scheduling&lt;/li&gt;
&lt;li&gt;quiz mode&lt;/li&gt;
&lt;li&gt;source-linked Mermaid nodes&lt;/li&gt;
&lt;li&gt;multi-file note ingestion&lt;/li&gt;
&lt;li&gt;confidence scoring per flashcard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data model already supports many of these extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;ContextGuard AI started as a study productivity idea, but the more interesting part became the architecture around &lt;strong&gt;grounded generation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The lesson from this project is simple:&lt;/p&gt;

&lt;p&gt;If you want AI features to feel useful in a real product, you need more than generation.&lt;/p&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;traceability&lt;/li&gt;
&lt;li&gt;graceful failure states&lt;/li&gt;
&lt;li&gt;good UX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is what turns “AI output” into a tool users can actually work with.&lt;/p&gt;

</description>
      <category>mern</category>
      <category>react</category>
      <category>ai</category>
      <category>mermaid</category>
    </item>
  </channel>
</rss>
