<?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: Amandeep Singh</title>
    <description>The latest articles on DEV Community by Amandeep Singh (@singhamandeep007).</description>
    <link>https://dev.to/singhamandeep007</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%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg</url>
      <title>DEV Community: Amandeep Singh</title>
      <link>https://dev.to/singhamandeep007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/singhamandeep007"/>
    <language>en</language>
    <item>
      <title>Making a dog talk with zero backend. 🐶✨

Built an entirely client-side pipeline to animate talking pets:
 - ✂️ In-browser image segmentation
 - 👄 Real-time WebGL lip-syncing
 - 🎬 Native MP4 export (all in the browser)

No servers, no GPU compute bills</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Mon, 17 Aug 2026 04:47:46 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-built-an-entirely-client-side-pipeline-to-animate-3mkd</link>
      <guid>https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-built-an-entirely-client-side-pipeline-to-animate-3mkd</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o" class="crayons-story__hidden-navigation-link"&gt;Making a dog talk with zero backend: in-browser segmentation, WebGL lip sync and real MP4 export&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;DEV Weekend Challenge: Dog Days Edition Submission 🐕&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4410624" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 16&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o" id="article-link-4410624"&gt;
          Making a dog talk with zero backend: in-browser segmentation, WebGL lip sync and real MP4 export
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/weekendchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;weekendchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>animation</category>
      <category>browser</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Making a dog talk with zero backend: in-browser segmentation, WebGL lip sync and real MP4 export</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 16 Aug 2026 14:55:43 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o</link>
      <guid>https://dev.to/singhamandeep007/making-a-dog-talk-with-zero-backend-in-browser-segmentation-webgl-lip-sync-and-real-mp4-export-1i4o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My dog has opinions. She has never once shared them.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;BarkReels&lt;/strong&gt;: you upload a photo, and it gives her a voice. The mouth actually moves with the audio, she blinks, her ears twitch, subtitles burn in, and you get a real MP4 at the end.&lt;/p&gt;

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


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/4t_R9VvtqZY"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


&lt;p&gt;The goal was to see how far a serious video pipeline could go with &lt;strong&gt;no backend at all&lt;/strong&gt;. No upload, no render queue, no "your video will be ready in 4 minutes" email. Your photo goes to the vision API you picked and nowhere else. Cutting your dog out of the background, animating her, and encoding the MP4 all happen on your own machine, with your own GPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you get to play with
&lt;/h3&gt;

&lt;p&gt;Everything here is a control in the UI, not a config file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the words come from.&lt;/strong&gt; You pick before analysis, so the vision model only does the job you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let the AI write the monologue from the photo&lt;/li&gt;
&lt;li&gt;Write the script yourself, pick a voice&lt;/li&gt;
&lt;li&gt;Record your own voice and use it directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Voice personas:&lt;/strong&gt; Deep &amp;amp; Wise 🧙, Playful Pup 🎾, Dramatic 🎭, Sassy Diva 💅.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Animation styles,&lt;/strong&gt; least to most motion:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preset&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🗿 Locked Off&lt;/td&gt;
&lt;td&gt;Only the face moves. The default, and most convincing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🍃 Barely There&lt;/td&gt;
&lt;td&gt;A whisper of drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎙️ Portrait&lt;/td&gt;
&lt;td&gt;Gentle sway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🙃 Belly Roll&lt;/td&gt;
&lt;td&gt;Lazy sideways flop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🏀 Bouncy&lt;/td&gt;
&lt;td&gt;Springy on the beat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;💨 Zoomies&lt;/td&gt;
&lt;td&gt;Maximum chaos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then sliders for mouth movement, head motion, emphasis nods, camera push-in, handheld shake, background parallax, blink rate and ear twitch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backgrounds:&lt;/strong&gt; blurred, original, your own uploaded image, plus procedural Sunset, Studio, Park, Neon and Solid. Your dog gets segmented out of the photo, so the background genuinely sits behind her and moves independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subtitles:&lt;/strong&gt; Karaoke (rolling window, highlighted word), Pop-up (two words, hard cuts) or Minimal (full sentence). Size, position, uppercase and colour are yours. The colour picker comes pre-loaded with &lt;strong&gt;shades sampled from your actual photo&lt;/strong&gt;, so a brown dog gets warm captions and a pink collar gets pink ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export:&lt;/strong&gt; MP4 or WebM at 1080x1920, 720x1280 or 1080x1080, 24 to 60fps.&lt;/p&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/1a304bf95a8d4621831a7a09256e76e7"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Getting the best results
&lt;/h3&gt;

&lt;p&gt;The animation moves specific anatomical points, so the photo matters more than you would expect. This took a lot of bad outputs to work out:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Head-on portrait.&lt;/strong&gt; Profile shots hide one eye and one ear, and the rig has nothing to animate on that side.&lt;br&gt;
✅ &lt;strong&gt;Face fills the frame.&lt;/strong&gt; Head and shoulders. In a full-body shot the muzzle is too small to deform cleanly.&lt;br&gt;
✅ &lt;strong&gt;Eyes, nose, mouth and ears clearly visible.&lt;/strong&gt; These are literally the coordinates the animation moves.&lt;br&gt;
✅ &lt;strong&gt;One dog.&lt;/strong&gt; Segmentation keeps a single subject. Two dogs become one blob.&lt;br&gt;
✅ &lt;strong&gt;Sharp and well lit.&lt;/strong&gt; Blur and deep shadow make the cutout edge mushy.&lt;br&gt;
❌ &lt;strong&gt;Nothing over the face.&lt;/strong&gt; Sunglasses, hands, toys. Anything covering an anchor gets deformed along with it.&lt;/p&gt;

&lt;p&gt;Two more things I learned the hard way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with Locked Off.&lt;/strong&gt; It is counterintuitive, but motion is what gives away a puppeted photo. Hold the frame completely still and the viewer reads it as real video of a dog sitting calmly, leaving only the mouth to judge. And the mouth is the one part driven by real audio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short scripts win.&lt;/strong&gt; Two or three punchy sentences. Long monologues drift out of sync with the face and burn your free tier faster.&lt;/p&gt;

&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%2Filj4fw5hrhlml7q5d503.png" 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%2Filj4fw5hrhlml7q5d503.png" alt="output" width="800" height="654"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://barkreels.vercel.app/" rel="noopener noreferrer"&gt;https://barkreels.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seriously, go try it. It is faster than finishing this post.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sign up at &lt;a href="https://elevenlabs.io/app/developers/api-keys" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt;, copy the API key&lt;/td&gt;
&lt;td&gt;~3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign up at &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; with your Google account, copy the key&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the app, paste both keys, drop in a dog photo&lt;/td&gt;
&lt;td&gt;~1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hit analyse, hit generate, hit export&lt;/td&gt;
&lt;td&gt;~4 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Both free tiers are enough.&lt;/strong&gt; No card required for either. The keys live in your browser's local storage and are sent only to the provider you picked, because there is no server of mine to send them to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⏳ &lt;strong&gt;What to expect on your first run.&lt;/strong&gt; Because background removal happens on your machine, the browser fetches the segmentation model before it can cut your dog out. That is roughly 25MB of ONNX runtime and neural network weights, and on a slow connection it can take a minute or two. You get a progress bar, and the download starts the moment you drop a photo in so it overlaps with everything else. &lt;strong&gt;It is a one-time cost.&lt;/strong&gt; Your browser caches it and every run after that goes straight to work. If the first one feels slow, that is what is happening, and it is not stuck.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No dog? Steal one from &lt;a href="https://unsplash.com/s/photos/dog-portrait" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;. It works on any dog and I will not tell anyone.&lt;/p&gt;


&lt;div&gt;
    &lt;iframe src="https://www.youtube.com/embed/_vRx81nNcs8"&gt;
    &lt;/iframe&gt;
  &lt;/div&gt;


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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/singhAmandeep007" rel="noopener noreferrer"&gt;
        singhAmandeep007
      &lt;/a&gt; / &lt;a href="https://github.com/singhAmandeep007/barkreels" rel="noopener noreferrer"&gt;
        barkreels
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Give your dog a voice 🐕
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;BarkReels 🐕 - client-only AI dog video generator&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Upload a photo of your dog. Get back a vertical short-form video where they
talk, breathe, blink, and nod - with burned-in subtitles and a real MP4 at the
end. Every pixel is rendered in your browser. There is no backend.&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/weekend-2026-08-13" rel="nofollow"&gt;DEV Weekend Challenge: Dog Days Edition&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it works&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;photo ──┬─→ vision model ──→ breed / mood / monologue / facial anchors
        └─→ ISNet (ONNX, in-browser) ──→ dog cutout + alpha

monologue ──→ ElevenLabs ──→ audio + word timestamps
                               │
                               ├─→ RMS envelope (asymmetric smoothing)
                               └─→ subtitle timing

envelope + anchors + cutout ──→ animation rig ──→ WebGL warp ──→ frames
                                                                  │
                                          WebCodecs H.264 + AAC ──┴──→ MP4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;The one architectural rule&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;&lt;code&gt;renderFrame(t)&lt;/code&gt; is a &lt;strong&gt;pure function of time&lt;/strong&gt;. It reads no clock, consults no
playback state, and mutates nothing that affects a later call.&lt;/p&gt;
&lt;p&gt;That…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/singhAmandeep007/barkreels" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;MIT licensed. Fork it, enable Pages in repo settings, and push: there is a GitHub Actions workflow that builds and deploys on every push to &lt;code&gt;main&lt;/code&gt;. Since there is no backend, static hosting is all it has ever needed.&lt;/p&gt;

&lt;p&gt;The interesting files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/render/rig.ts               the animation formula (pure functions)
src/render/glRenderer.ts        WebGL2 renderer, renderFrame(t)
src/render/backgroundPlate.ts   pull-push inpainting
src/render/exporter.ts          offline WebCodecs encode + muxing
src/services/vision.ts          Gemini and Ollama behind one interface
src/services/audioAnalysis.ts   envelope, onsets, PCM decode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;photo ─┬─→ Gemini ──→ breed, mood, monologue, facial anchor coordinates
       └─→ ISNet (ONNX, in-browser) ──→ dog cutout with alpha

monologue ──→ ElevenLabs ──→ audio + word-level timestamps
                                 ├─→ loudness envelope  ──→ drives the jaw
                                 └─→ word timings       ──→ drives subtitles

envelope + anchors + cutout ──→ animation rig ──→ WebGL ──→ WebCodecs ──→ MP4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Google AI does two jobs in one call.&lt;/strong&gt; Gemini Flash writes the monologue in the dog's voice &lt;em&gt;and&lt;/em&gt; acts as a precise vision annotator, returning normalised bounding boxes for the nose, mouth, eyes and ears. Those coordinates are what the shader deforms. When you supply your own script the prompt drops the writing job entirely, so the model spends all its attention on the coordinates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ElevenLabs is doing more than narration.&lt;/strong&gt; The &lt;code&gt;/with-timestamps&lt;/code&gt; endpoint returns character-level timings alongside the audio. Those timings drive the karaoke subtitles, and the loudness envelope derived from the same audio drives the jaw. The voice is not decoration here, it is the animation input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one rule that made everything else work:&lt;/strong&gt; &lt;code&gt;renderFrame(t)&lt;/code&gt; is a pure function of time. It reads no clock and consults no playback state. Preview is &lt;code&gt;requestAnimationFrame(() =&amp;gt; renderFrame(audio.currentTime))&lt;/code&gt;. Export is a plain &lt;code&gt;for&lt;/code&gt; loop over frame indices. Same code, same pixels, so the preview is an actual promise about the output. Export runs offline at roughly 2 to 3 times real time, and backgrounding the tab cannot corrupt it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lip sync is not a model.&lt;/strong&gt; It is the loudness envelope with deliberately asymmetric smoothing: 15ms attack, 90ms release. Muscles snap a jaw open and tissue eases it closed. Symmetric smoothing makes the mouth flutter shut between syllables like a VU meter, and that one asymmetry did more for perceived realism than anything else I tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; and &lt;strong&gt;Best Use of Google AI&lt;/strong&gt;. Both are wired into the animation rather than bolted on top of it: Gemini's bounding boxes are what the shader deforms, and ElevenLabs' word timings and loudness envelope are what drive the mouth and the subtitles. See the section above for the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would build next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-photo angle switching.&lt;/strong&gt; Upload and thumbnails work, but the renderer still uses one image. Cross-fading between angles on speech beats is the obvious next move.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better anchors for flat faces.&lt;/strong&gt; Pugs and bulldogs have a short muzzle and my fallback jaw hinge lands too close to the chin. A breed-aware hinge would fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Head turn.&lt;/strong&gt; The head is a flat layer today. A depth estimate would allow a small yaw and sell the 3D far harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-origin isolation.&lt;/strong&gt; ONNX runs single-threaded because enabling &lt;code&gt;COOP&lt;/code&gt;/&lt;code&gt;COEP&lt;/code&gt; would break the Gemini and ElevenLabs calls. Moving segmentation into an isolated worker would make it roughly 4 times faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tail wag and a real ground shadow&lt;/strong&gt; for full-body shots, which currently get no love at all.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Ten minutes, start to finish, and both free tiers cover it. If you make one, post it in the comments. I genuinely want to see what your dog has been holding back.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>Hey everyone! I recently wrapped up a project migrating 6 separate Go microservice repositories into a unified monorepo setup.

I documented the architecture decisions, pipeline setup, and lessons learned here.</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 09 Aug 2026 03:22:07 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/hey-everyone-i-recently-wrapped-up-a-project-migrating-6-separate-go-microservice-repositories-5d1d</link>
      <guid>https://dev.to/singhamandeep007/hey-everyone-i-recently-wrapped-up-a-project-migrating-6-separate-go-microservice-repositories-5d1d</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88" class="crayons-story__hidden-navigation-link"&gt;Multi-Repo to Monorepo: How I Automated 6 Go Microservice Releases and Then Made It 15x Faster&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4314700" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 7&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88" id="article-link-4314700"&gt;
          Multi-Repo to Monorepo: How I Automated 6 Go Microservice Releases and Then Made It 15x Faster
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/monorepo"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;monorepo&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;7&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>architecture</category>
      <category>devops</category>
      <category>go</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Multi-Repo to Monorepo: How I Automated 6 Go Microservice Releases and Then Made It 15x Faster</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Fri, 07 Aug 2026 17:47:23 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88</link>
      <guid>https://dev.to/singhamandeep007/multi-repo-to-monorepo-how-i-automated-6-go-microservice-releases-and-then-made-it-15x-faster-4e88</guid>
      <description>&lt;p&gt;Last month I spent more than an hour cutting a release across six Go microservice repos. Tag log, wait for CI. Update sdk's &lt;code&gt;go.mod&lt;/code&gt; to point at the new log SHA, push, wait for CI. Repeat for utils. Then do api, cli, and worker in parallel - except I forgot to bump cli's dependency and the build broke at 11pm.&lt;/p&gt;

&lt;p&gt;That was the last manual release I did.&lt;/p&gt;

&lt;p&gt;This is the story of automating that entire workflow with Jenkins + Python + GitLab, then realizing the multi-repo architecture was the real problem, and collapsing everything into a Go monorepo that's &lt;strong&gt;15x faster&lt;/strong&gt; at cutting releases.&lt;/p&gt;

&lt;p&gt;The full setup runs on my laptop. You can fork it and try it yourself.&lt;/p&gt;

&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%2Fkaindp8dirfk3ghecpqn.png" 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%2Fkaindp8dirfk3ghecpqn.png" alt=" " width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Six Modules&lt;/li&gt;
&lt;li&gt;The Stack&lt;/li&gt;
&lt;li&gt;Phase 1: Multi-Repo Automation&lt;/li&gt;
&lt;li&gt;Phase 2: The Monorepo Pivot&lt;/li&gt;
&lt;li&gt;The Unified CI Pipeline&lt;/li&gt;
&lt;li&gt;Real Numbers&lt;/li&gt;
&lt;li&gt;Caveats and Gotchas&lt;/li&gt;
&lt;li&gt;Try It Yourself&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Six Modules
&lt;/h2&gt;

&lt;p&gt;The project simulates a real production system with six Go modules that have strict dependency ordering:&lt;/p&gt;

&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%2Fa6d8ypj01136dwwa1s34.png" 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%2Fa6d8ypj01136dwwa1s34.png" alt=" " width="800" height="757"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Tag Scheme&lt;/th&gt;
&lt;th&gt;Depends On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Logger (leaf, no deps)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v0.x.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sdk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API client&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v0.x.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;utils&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shared utilities&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v0.x.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log, sdk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api/backend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;APP-x.y.z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log, utils&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cli-x.y.z&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log, sdk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;worker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Background&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v0.x.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;log, utils&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first three modules are &lt;strong&gt;sequential&lt;/strong&gt; - sdk can't tag until log is tagged, utils can't tag until sdk is tagged. The last three are &lt;strong&gt;terminal&lt;/strong&gt; - they can process in parallel once the sequential chain is done.&lt;/p&gt;

&lt;p&gt;Every module lives on three long-lived branches: &lt;code&gt;develop&lt;/code&gt; → &lt;code&gt;release&lt;/code&gt; → &lt;code&gt;master&lt;/code&gt;. A release means moving code through all three, in all six repos, in the right order.&lt;/p&gt;

&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%2F4lstgjsl6vqo0w09w5k1.png" 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%2F4lstgjsl6vqo0w09w5k1.png" alt=" " width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the problem. Do it manually and you're juggling 6 repos × 3 branches × dependency ordering. One forgotten &lt;code&gt;go mod tidy&lt;/code&gt; and you're debugging at midnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;Everything runs on a MacBook. No cloud CI, no SaaS - just local tools wired together.&lt;/p&gt;

&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%2Fho8dk1zhnr888xm1l8od.png" 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%2Fho8dk1zhnr888xm1l8od.png" alt=" " width="800" height="498"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; MacBook                                         GitLab.com
+-------------------+    ngrok tunnel    +------------------------+
|  Jenkins LTS      | &amp;lt;===============&amp;gt; |  Webhooks (push / MR)  |
|  (brew service)   |                   |  Commit status API     |
|  :8080            | ================&amp;gt; |  6 repos (multi-repo)  |
|                   |   git push,       |  - or -                |
|  Python 3 + Go    |   API calls       |  1 repo  (monorepo)    |
+-------------------+                   +------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The key pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jenkins LTS&lt;/strong&gt; via &lt;code&gt;brew install jenkins-lts&lt;/code&gt;, running as a launched service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ngrok&lt;/strong&gt; exposing &lt;code&gt;localhost:8080&lt;/code&gt; → &lt;code&gt;https://enclosure-version-moocher.ngrok-free.dev/&lt;/code&gt; so GitLab integration/webhook can reach Jenkins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab.com&lt;/strong&gt; hosting the repos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; (&lt;code&gt;python-gitlab&lt;/code&gt; + &lt;code&gt;GitPython&lt;/code&gt;) for the automation engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go 1.24&lt;/strong&gt; with workspace support (&lt;code&gt;go.work&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&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%2F2mnj9txztca18qygfwr6.png" 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%2F2mnj9txztca18qygfwr6.png" alt=" " width="799" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2Ftycqfnjnfst5eshlalqh.png" 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%2Ftycqfnjnfst5eshlalqh.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Jenkins ↔ GitLab Integration
&lt;/h3&gt;

&lt;p&gt;Configured using GitLab’s native Jenkins Integration combined with ngrok for local tunneling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitLab → Jenkins: Native integration automatically triggers local Jenkins pipelines on push and MR events.&lt;/li&gt;
&lt;li&gt;Jenkins → GitLab: Reports build progress and status back via API, displaying green/red status checks directly on commits and merge requests.&lt;/li&gt;
&lt;/ol&gt;

&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%2Ftwmv2rva18jqtk343yso.png" 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%2Ftwmv2rva18jqtk343yso.png" alt=" " width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2Fkj2niu4i8m7osvovfxz7.png" 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%2Fkj2niu4i8m7osvovfxz7.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Phase 1: Multi-Repo Automation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Setup: 6 Repos, 7 Jenkins Jobs
&lt;/h3&gt;

&lt;p&gt;In the multi-repo world, each module is its own GitLab repo under the &lt;code&gt;multirepo-release/&lt;/code&gt; group:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multirepo-release/
  ├── log/          → gitlab.com:multirepo-release/log.git
  ├── sdk/          → gitlab.com:multirepo-release/sdk.git
  ├── utils/        → gitlab.com:multirepo-release/utils.git
  ├── api/          → gitlab.com:multirepo-release/api.git
  ├── cli/          → gitlab.com:multirepo-release/cli.git
  ├── worker/       → gitlab.com:multirepo-release/worker.git
  └── pipelines/    → gitlab.com:multirepo-release/pipelines.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each repo gets its own Jenkins CI job (6 jobs), plus one orchestration job that runs the automation Python script. That's &lt;strong&gt;7 Jenkins jobs&lt;/strong&gt; to maintain.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Automation Engine: 1,800 Lines of Python
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;pipelines/&lt;/code&gt; repo contains the brain: a Python script (&lt;code&gt;poc_automation.py&lt;/code&gt;) that talks to the GitLab API and performs git operations. One Jenkins job, five actions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;release_cut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merges develop → release for all 6 repos, in dependency order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backmerge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merges release → develop (or master → release)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rc_create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merges release → master, tags each module, bumps go.mod versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;version_bump&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bumps version constants in api and cli on develop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compute_versions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calculates next semver tags based on existing tags&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&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%2Fyuwi1nykyfhrrgw376x5.png" 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%2Fyuwi1nykyfhrrgw376x5.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How a Release Cut Actually Works
&lt;/h3&gt;

&lt;p&gt;When you trigger &lt;code&gt;release_cut&lt;/code&gt;, the script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sequential phase&lt;/strong&gt; - processes log, sdk, utils one at a time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates an intermediate branch from &lt;code&gt;release&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Merges &lt;code&gt;develop&lt;/code&gt; into it (handling go.mod conflicts automatically)&lt;/li&gt;
&lt;li&gt;Creates a GitLab MR&lt;/li&gt;
&lt;li&gt;Waits for approval (or auto-merges if configured)&lt;/li&gt;
&lt;li&gt;Records the merged SHA - the next repo needs it for &lt;code&gt;go mod tidy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parallel phase&lt;/strong&gt; - processes api, cli, worker simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same merge flow, but all three run concurrently since they're terminal nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hardest part: &lt;strong&gt;cross-repo dependency tracking&lt;/strong&gt;. When log merges to release, sdk needs to run &lt;code&gt;go get gitlab.com/multirepo-release/log@&amp;lt;new-SHA&amp;gt;&lt;/code&gt; to pin to the exact commit. The script tracks these SHAs in &lt;code&gt;self.merged_target_shas&lt;/code&gt; and passes them forward through the chain.&lt;/p&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/b5f80ad7a81c45bdbebb533904c98624"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Conflict Handling
&lt;/h3&gt;

&lt;p&gt;When code conflicts happen (not just go.mod), the pipeline doesn't crash. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a conflict branch cut from the &lt;em&gt;source&lt;/em&gt; (so GitLab renders the real diff)&lt;/li&gt;
&lt;li&gt;Opens an MR with &lt;code&gt;[CONFLICT]&lt;/code&gt; in the title&lt;/li&gt;
&lt;li&gt;Polls every 30 seconds for manual resolution + approval&lt;/li&gt;
&lt;li&gt;Auto-merges once both conditions are met&lt;/li&gt;
&lt;li&gt;Continues the pipeline&lt;/li&gt;
&lt;/ol&gt;

&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%2F09fv9zhwdmmfh6nwxu82.png" 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%2F09fv9zhwdmmfh6nwxu82.png" alt=" " width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The HTML Report
&lt;/h3&gt;

&lt;p&gt;Every automation run generates a styled HTML report archived in Jenkins. It logs every action taken: branches created, MRs opened, approvals received, tags applied.&lt;/p&gt;

&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%2Fee7itwt4yfl4cxcbs9cc.png" 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%2Fee7itwt4yfl4cxcbs9cc.png" alt=" " width="800" height="978"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: The Monorepo Pivot
&lt;/h2&gt;

&lt;p&gt;The multi-repo automation worked. But maintaining it was its own job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7 Jenkins jobs&lt;/strong&gt; to configure and monitor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 &lt;code&gt;Jenkinsfile.ci&lt;/code&gt; files&lt;/strong&gt; (103 lines each, identical except for the module name)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1,800 lines of Python&lt;/strong&gt; managing 6 separate GitLab projects, tracking SHAs across repos, handling per-repo cloning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 MRs per release cut&lt;/strong&gt; - one per repo, each needing approval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question became: what if all six modules lived in one repo?&lt;/p&gt;

&lt;h3&gt;
  
  
  go.work: The Go Workspace
&lt;/h3&gt;

&lt;p&gt;Go 1.18 introduced workspaces (&lt;code&gt;go.work&lt;/code&gt;), which let multiple modules in a single repo resolve each other locally during development while still being independently importable by external consumers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// go.work - the workspace file at the repo root&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.26&lt;/span&gt;

&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;backend&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;
    &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;replace&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singhamandeep007&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;monorepo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.3.0&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
    &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singhamandeep007&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;monorepo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.3.0&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;
    &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singhamandeep007&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;monorepo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.3.0&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;replace&lt;/code&gt; directives are the key insight: during local development and CI, Go resolves internal dependencies from the local directory. But when an external project runs &lt;code&gt;go get gitlab.com/.../monorepo/log@v0.3.0&lt;/code&gt;, Go fetches the tagged version from GitLab. Best of both worlds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-Module Tagging in a Monorepo
&lt;/h3&gt;

&lt;p&gt;Go's module system supports directory-prefixed tags. In a monorepo, each module gets its own tag with a path prefix:&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="c"&gt;# Each module tagged independently, on the same commit&lt;/span&gt;
git tag log/v0.3.0
git tag sdk/v0.3.0
git tag utils/v0.3.0
git tag api/backend/APP-1.3.0
git tag cli/cli-1.3.0
git tag worker/v0.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone runs &lt;code&gt;go get gitlab.com/singhamandeep007-group/monorepo/sdk@v0.3.0&lt;/code&gt;, Go looks for the tag &lt;code&gt;sdk/v0.3.0&lt;/code&gt; - the module path prefix matches the tag prefix. Each module can version independently while living in the same repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Simplified
&lt;/h3&gt;

&lt;p&gt;The monorepo automation (&lt;code&gt;monorepo-pipelines/poc_automation.py&lt;/code&gt;) dropped from &lt;strong&gt;1,762 to 1,143 lines&lt;/strong&gt; - a 35% reduction. Here's what went away:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Multi-repo complexity&lt;/th&gt;
&lt;th&gt;Monorepo equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6 separate GitLab project objects&lt;/td&gt;
&lt;td&gt;1 project object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clone + manage 6 local git repos&lt;/td&gt;
&lt;td&gt;No local cloning needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Track &lt;code&gt;merged_target_shas&lt;/code&gt; across repos&lt;/td&gt;
&lt;td&gt;Not needed - same repo, same SHA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;go get module@SHA&lt;/code&gt; + &lt;code&gt;go mod tidy&lt;/code&gt; per repo&lt;/td&gt;
&lt;td&gt;GitLab file API to edit go.mod directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6 MRs per release cut&lt;/td&gt;
&lt;td&gt;1 MR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sync_dependencies&lt;/code&gt; action&lt;/td&gt;
&lt;td&gt;Not needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Third-Party Consumption
&lt;/h3&gt;

&lt;p&gt;An external project can import any module from the monorepo individually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// third-party project's go.mod&lt;/span&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singhamandeep007&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.26&lt;/span&gt;

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;singhamandeep007&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;monorepo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.3.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"gitlab.com/singhamandeep007-group/monorepo/log"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using log from the monorepo"&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;The consumer doesn't need the entire monorepo - Go fetches only the &lt;code&gt;log&lt;/code&gt; module at the tagged version.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Unified CI Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One Pipeline, All Modules, In Parallel
&lt;/h3&gt;

&lt;p&gt;The monorepo CI (&lt;code&gt;jenkins/ci.Jenkinsfile&lt;/code&gt;, 324 lines) replaces 6 identical per-repo &lt;code&gt;Jenkinsfile.ci&lt;/code&gt; files (636 lines total). It fans out all six modules in parallel:&lt;/p&gt;

&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%2Fr1wtw8dx0huejtrkw4d0.png" 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%2Fr1wtw8dx0huejtrkw4d0.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each module, in parallel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;go vet ./...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;go test -count=1 -race -covermode=atomic&lt;/code&gt; → JUnit XML + coverage HTML&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;govulncheck&lt;/code&gt; → JSON + human-readable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;go build&lt;/code&gt; (only for &lt;code&gt;api/backend&lt;/code&gt; and &lt;code&gt;cli&lt;/code&gt;) → multi-arch binaries + SHA256SUMS
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"All Packages"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;parallel&lt;/span&gt; &lt;span class="n"&gt;pkgStages&lt;/span&gt;  &lt;span class="c1"&gt;// 6 modules run simultaneously&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All six run with &lt;code&gt;failFast: false&lt;/code&gt; - a failure in one module doesn't mask problems in others. The pipeline produces an aggregated &lt;code&gt;ci-report.html&lt;/code&gt; showing every module's test results, coverage percentage, and vulnerability count in one page.&lt;/p&gt;

&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%2Fak5omtpgcemnlq0rbrdc.png" 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%2Fak5omtpgcemnlq0rbrdc.png" alt=" " width="800" height="967"&gt;&lt;/a&gt;&lt;/p&gt;

&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%2Ff8f4eo5zt0iw1ck3bhdy.png" 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%2Ff8f4eo5zt0iw1ck3bhdy.png" alt=" " width="799" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Arch Builds
&lt;/h3&gt;

&lt;p&gt;Terminal binaries (&lt;code&gt;api/backend&lt;/code&gt;, &lt;code&gt;cli&lt;/code&gt;) build for three targets:&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="c"&gt;# CGO_ENABLED=0 for cross-compilation (no C dependencies)&lt;/span&gt;
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;darwin &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 &lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; bin/api-darwin-arm64
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux  &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 &lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; bin/api-linux-amd64
&lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux  &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64 &lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build &lt;span class="nt"&gt;-o&lt;/span&gt; bin/api-linux-arm64

&lt;span class="c"&gt;# SHA256 checksums for verification&lt;/span&gt;
shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 bin/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bin/SHA256SUMS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&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%2Fi9stlnd5tz8sn8l8zdmu.png" 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%2Fi9stlnd5tz8sn8l8zdmu.png" alt=" " width="800" height="665"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Release Automation Pipeline
&lt;/h3&gt;

&lt;p&gt;The release automation lives in a &lt;strong&gt;separate repo&lt;/strong&gt; (&lt;code&gt;monorepo-pipelines&lt;/code&gt;) - keeping release tooling isolated from the application code. Same five actions as before, but operating on a single repo:&lt;/p&gt;

&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%2Ff901hsnivwp99r6rm9ai.png" 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%2Ff901hsnivwp99r6rm9ai.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After an RC, the script tags each module and updates &lt;code&gt;go.mod&lt;/code&gt; + &lt;code&gt;go.work&lt;/code&gt; via the GitLab file API - no local cloning needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Numbers
&lt;/h2&gt;

&lt;p&gt;These come from &lt;strong&gt;451 multi-repo builds&lt;/strong&gt; and &lt;strong&gt;36 monorepo builds&lt;/strong&gt; on the same Jenkins instance, running the same Go modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release Automation Speed
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Multi-Repo (avg)&lt;/th&gt;
&lt;th&gt;Monorepo (avg)&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;release_cut&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,170s&lt;/strong&gt; (~19 min)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;75s&lt;/strong&gt; (~1 min)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15.5x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backmerge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;688s&lt;/strong&gt; (~11 min)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19.4x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rc_create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;501s&lt;/strong&gt; (~8 min)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;73s&lt;/strong&gt; (~1 min)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.9x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The multi-repo release_cut takes ~19 minutes because it processes 6 repos sequentially (3 sequential + 3 parallel), creating 6 MRs, waiting for each CI to pass, and tracking SHAs across repos. The monorepo version creates 1 MR and is done in ~75 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI Pipeline
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Multi-Repo&lt;/th&gt;
&lt;th&gt;Monorepo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins jobs&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;7&lt;/strong&gt; (6 CI + 1 orchestration)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2&lt;/strong&gt; (1 CI + 1 automation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI wall time&lt;/td&gt;
&lt;td&gt;~132s (sequential across 6 jobs)&lt;/td&gt;
&lt;td&gt;~55s (parallel in 1 job)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkinsfile maintenance&lt;/td&gt;
&lt;td&gt;6 × 103 lines (identical)&lt;/td&gt;
&lt;td&gt;1 × 324 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MRs per release cut&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Codebase
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Multi-Repo&lt;/th&gt;
&lt;th&gt;Monorepo&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automation Python&lt;/td&gt;
&lt;td&gt;1,800 lines&lt;/td&gt;
&lt;td&gt;1,143 lines&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-36%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI pipeline config&lt;/td&gt;
&lt;td&gt;618 lines (6 files)&lt;/td&gt;
&lt;td&gt;324 lines (1 file)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-48%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins orchestration&lt;/td&gt;
&lt;td&gt;274 lines&lt;/td&gt;
&lt;td&gt;231 lines&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-16%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total pipeline code&lt;/td&gt;
&lt;td&gt;2,692 lines&lt;/td&gt;
&lt;td&gt;1,698 lines&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-37%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&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%2F8a9jxcn0ouhpp4s4siu4.png" 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%2F8a9jxcn0ouhpp4s4siu4.png" alt=" " width="799" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Total Build History
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Multi-Repo&lt;/th&gt;
&lt;th&gt;Monorepo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI builds&lt;/td&gt;
&lt;td&gt;385&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automation builds&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total builds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;451&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins jobs to maintain&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Caveats and Gotchas
&lt;/h2&gt;

&lt;p&gt;A few things that bit me along the way - saving you the debugging time:&lt;/p&gt;

&lt;h3&gt;
  
  
  go.work replace versions must match go.mod exactly
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;sdk/go.mod&lt;/code&gt; says &lt;code&gt;require log v0.5.0&lt;/code&gt; but &lt;code&gt;go.work&lt;/code&gt; has &lt;code&gt;replace log v0.3.0 =&amp;gt; ./log&lt;/code&gt;, Go ignores the replace and tries to fetch &lt;code&gt;log v0.5.0&lt;/code&gt; from the remote. The versions must match exactly. This caused CI failures on all three branches when our RC automation bumped go.mod versions without creating matching tags.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins doesn't load shell profiles
&lt;/h3&gt;

&lt;p&gt;If you install Go via &lt;code&gt;asdf&lt;/code&gt; or a version manager, Jenkins won't find it - it runs a non-login, non-interactive shell. Set &lt;code&gt;GOROOT&lt;/code&gt; and &lt;code&gt;PATH&lt;/code&gt; explicitly in the pipeline environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GOROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/usr/local/go'&lt;/span&gt;
    &lt;span class="n"&gt;PATH&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/go/bin:${env.HOME}/go/bin:${env.PATH}"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Private modules need three environment variables
&lt;/h3&gt;

&lt;p&gt;For private GitLab-hosted Go modules, &lt;code&gt;GOPRIVATE&lt;/code&gt; alone isn't enough. You also need &lt;code&gt;GONOSUMCHECK&lt;/code&gt; and &lt;code&gt;GONOSUMDB&lt;/code&gt; to prevent checksum database lookups that will fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;GOPRIVATE&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gitlab.com/your-group/*'&lt;/span&gt;
&lt;span class="n"&gt;GONOSUMCHECK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gitlab.com/your-group/*'&lt;/span&gt;
&lt;span class="n"&gt;GONOSUMDB&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gitlab.com/your-group/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CGO and cross-compilation don't mix (on macOS)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CGO_ENABLED=1&lt;/code&gt; is needed for &lt;code&gt;-race&lt;/code&gt; in tests, but cross-compiling to &lt;code&gt;linux/amd64&lt;/code&gt; on macOS with CGO on fails because there's no Linux C toolchain. Solution: &lt;code&gt;CGO_ENABLED=1&lt;/code&gt; globally for tests, &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; per build command:&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="c"&gt;# Tests: CGO on (for -race)&lt;/span&gt;
&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-race&lt;/span&gt; ./...

&lt;span class="c"&gt;# Builds: CGO off (for cross-compilation)&lt;/span&gt;
&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux &lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;amd64 go build &lt;span class="nt"&gt;-o&lt;/span&gt; bin/api-linux-amd64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Groovy triple-quoted strings mangle Python escape sequences
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;\n&lt;/code&gt; inside a Groovy &lt;code&gt;"""..."""&lt;/code&gt; string becomes a literal newline before the embedded Python sees it, causing &lt;code&gt;SyntaxError: EOL while scanning string literal&lt;/code&gt;. Use &lt;code&gt;chr(10)&lt;/code&gt; and &lt;code&gt;chr(9)&lt;/code&gt; instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Breaks: Groovy converts \n to a real newline
&lt;/span&gt;&lt;span class="n"&gt;line&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="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;FAIL&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Works: chr() bypasses Groovy's string processing
&lt;/span&gt;&lt;span class="n"&gt;line&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="nf"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAIL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Both repos are on GitLab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://gitlab.com/singhamandeep007-group/monorepo" rel="noopener noreferrer"&gt;Monorepo&lt;/a&gt;&lt;/strong&gt; (Go source + CI): &lt;code&gt;gitlab.com/singhamandeep007-group/monorepo&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://gitlab.com/singhamandeep007-group/monorepo-pipelines" rel="noopener noreferrer"&gt;Monorepo Pipelines&lt;/a&gt;&lt;/strong&gt; (release automation): &lt;code&gt;gitlab.com/singhamandeep007-group/monorepo-pipelines&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://gitlab.com/multirepo-release" rel="noopener noreferrer"&gt;Multi-repo version&lt;/a&gt;&lt;/strong&gt; (for comparison): &lt;code&gt;gitlab.com/multirepo-release/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install Jenkins&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;jenkins-lts
brew services start jenkins-lts

&lt;span class="c"&gt;# 2. Install ngrok and start tunnel&lt;/span&gt;
ngrok http 8080 &lt;span class="nt"&gt;--url&lt;/span&gt; your-subdomain.ngrok-free.dev

&lt;span class="c"&gt;# 3. Clone and explore&lt;/span&gt;
git clone git@gitlab.com:singhamandeep007-group/monorepo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;monorepo
go work &lt;span class="nb"&gt;sync
&lt;/span&gt;make test-all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Next
&lt;/h3&gt;

&lt;p&gt;This is a POC - production would need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets management&lt;/strong&gt;: rotate GitLab tokens, use Jenkins credential rotation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent scaling&lt;/strong&gt;: Jenkins agents instead of running everything on the controller&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification&lt;/strong&gt;: Slack/email integration for merge approvals and failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: Go module cache persistence across builds&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>go</category>
      <category>devops</category>
      <category>automation</category>
      <category>monorepo</category>
    </item>
    <item>
      <title>Wanted Claude to read my OneNote notebooks. Grabbed an MCP server, hit a silent 401 on every call.

Wrote up the full debugging. Check it out</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sat, 25 Jul 2026 17:45:15 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/wanted-claude-to-read-my-onenote-notebooks-grabbed-an-mcp-server-hit-a-silent-401-on-every-301n</link>
      <guid>https://dev.to/singhamandeep007/wanted-claude-to-read-my-onenote-notebooks-grabbed-an-mcp-server-hit-a-silent-401-on-every-301n</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-story__hidden-navigation-link"&gt;I Rewrote a OneNote MCP Server in TypeScript — Here's What I Learned About Microsoft Graph Auth&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Scope pitfalls and silent token renewal&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-4179498" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" id="article-link-4179498"&gt;
          I Rewrote a OneNote MCP Server in TypeScript — Here's What I Learned About Microsoft Graph Auth
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              10&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>debugging</category>
      <category>mcp</category>
      <category>microsoft</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Onenote MCP server rewritten - awesome</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 19 Jul 2026 11:59:01 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/onenote-mcp-server-rewritten-awesome-2ph9</link>
      <guid>https://dev.to/singhamandeep007/onenote-mcp-server-rewritten-awesome-2ph9</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-story__hidden-navigation-link"&gt;I Rewrote a OneNote MCP Server in TypeScript — Here's What I Learned About Microsoft Graph Auth&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Scope pitfalls and silent token renewal&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-4179498" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 19&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" id="article-link-4179498"&gt;
          I Rewrote a OneNote MCP Server in TypeScript — Here's What I Learned About Microsoft Graph Auth
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;13&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              10&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>mcp</category>
      <category>microsoft</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I Rewrote a OneNote MCP Server in TypeScript — Here's What I Learned About Microsoft Graph Auth</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 19 Jul 2026 11:57:24 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933</link>
      <guid>https://dev.to/singhamandeep007/i-rewrote-a-onenote-mcp-server-in-typescript-heres-what-i-learned-about-microsoft-graph-auth-5933</guid>
      <description>&lt;p&gt;If you use Claude, Cursor, or any MCP-compatible AI assistant, you've probably noticed how useful it is when your AI can actually &lt;em&gt;read&lt;/em&gt; your notes. I wanted that for OneNote — so I grabbed an existing MCP server, hit a cryptic 401 error, and ended up rewriting the whole thing in TypeScript.&lt;/p&gt;

&lt;p&gt;Here's what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's an MCP Server?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP) is a standard that lets AI assistants call external tools over JSON-RPC. You write a server that exposes tools (like &lt;code&gt;listNotebooks&lt;/code&gt; or &lt;code&gt;createPage&lt;/code&gt;), and any MCP client — Claude Desktop, Cursor, Claude Code — can invoke them.&lt;/p&gt;

&lt;p&gt;The server communicates over stdio: JSON-RPC on stdout, diagnostics on stderr. This is a critical detail that will matter later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Starting Point
&lt;/h2&gt;

&lt;p&gt;I started with &lt;a href="https://github.com/danosb/onenote-mcp" rel="noopener noreferrer"&gt;danosb/onenote-mcp&lt;/a&gt;, a JavaScript MCP server that uses Microsoft Graph to access OneNote. It had the right idea — device-code auth so you don't need to register an Azure app — but I hit a wall immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 401 That Broke Everything
&lt;/h2&gt;

&lt;p&gt;After authenticating with my personal Microsoft account, every Graph API call returned:&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;HTTP 401 — "The request does not contain a valid authentication token"
(error code 40001)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The token &lt;em&gt;looked&lt;/em&gt; fine. Authentication completed successfully. But Graph rejected every request.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Root Cause: &lt;code&gt;.All&lt;/code&gt; Scopes
&lt;/h3&gt;

&lt;p&gt;The original server requested these scopes:&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;scopes&lt;/span&gt; &lt;span class="o"&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;Notes.Read.All&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;Notes.ReadWrite.All&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;User.Read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Those &lt;code&gt;.All&lt;/code&gt; scopes are &lt;strong&gt;application-level permissions&lt;/strong&gt;. Personal Microsoft accounts (MSA) cannot consent to them — only Azure AD work/school accounts can. When a personal account tries to use them, Azure &lt;em&gt;does&lt;/em&gt; issue a token, but it's a token that Microsoft Graph won't accept.&lt;/p&gt;

&lt;p&gt;No error during auth. No warning. Just a silent 401 on every subsequent call.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Replace &lt;code&gt;.All&lt;/code&gt; scopes with resource-qualified delegated scopes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&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;https://graph.microsoft.com/Notes.Read&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;https://graph.microsoft.com/Notes.ReadWrite&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;https://graph.microsoft.com/User.Read&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;offline_access&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// needed for refresh token&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// needed for id_token (username)&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These are delegated scopes that work for &lt;em&gt;both&lt;/em&gt; personal and work/school accounts. They grant access to the signed-in user's own OneNote content, which is exactly what you want for an MCP server. The &lt;code&gt;offline_access&lt;/code&gt; scope is critical — without it, you won't get a refresh token and users will have to re-authenticate every hour.&lt;/p&gt;
&lt;h3&gt;
  
  
  Bonus Gotcha: Non-JWT Tokens
&lt;/h3&gt;

&lt;p&gt;Personal Microsoft accounts return &lt;strong&gt;compact (non-JWT) tokens&lt;/strong&gt; — opaque strings that don't have the three-segment &lt;code&gt;header.payload.signature&lt;/code&gt; structure. If your code validates token format by checking for JWTs, it will incorrectly reject perfectly valid personal-account tokens.&lt;/p&gt;

&lt;p&gt;The right approach: don't validate token format at all. Let Microsoft Graph be the authority on whether a token is valid.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a Full TypeScript Rewrite?
&lt;/h2&gt;

&lt;p&gt;Once I fixed the auth bug, I looked at the codebase and found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~10 standalone JavaScript files doing overlapping things (&lt;code&gt;simple-onenote.js&lt;/code&gt;, &lt;code&gt;list-sections.js&lt;/code&gt;, &lt;code&gt;list-pages.js&lt;/code&gt;, &lt;code&gt;get-page.js&lt;/code&gt;, &lt;code&gt;get-page-content.js&lt;/code&gt;, &lt;code&gt;get-all-page-contents.js&lt;/code&gt;...)&lt;/li&gt;
&lt;li&gt;MCP tools with no parameter schemas — everything came in through &lt;code&gt;params.random_string&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A dependency on &lt;code&gt;jsdom&lt;/code&gt; just for HTML-to-text conversion&lt;/li&gt;
&lt;li&gt;A dependency on &lt;code&gt;node-fetch&lt;/code&gt; (unnecessary with Node 18+ native fetch)&lt;/li&gt;
&lt;li&gt;The deprecated &lt;code&gt;Client.init&lt;/code&gt; callback pattern instead of &lt;code&gt;Client.initWithMiddleware&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I rewrote it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The New Architecture
&lt;/h2&gt;

&lt;p&gt;Everything lives in &lt;code&gt;src/&lt;/code&gt; with clear separation:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  config.ts         — Client ID, tenant, scopes, paths
  logger.ts         — stderr-only logging (stdout = JSON-RPC)
  token-store.ts    — Token normalization utilities
  auth.ts           — OAuth 2.0 device-code auth + silent token refresh
  graph-client.ts   — Graph SDK client factory
  html.ts           — HTML→text (dependency-free)
  onenote.ts        — OneNoteClient class
  mcp-server.ts     — MCP server with Zod-typed tools
  cli.ts            — Unified CLI (replaces 10 scripts)
  index.ts          — Barrel export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Design Decisions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Zod schemas on every tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The old server used the SDK's &lt;code&gt;tool()&lt;/code&gt; method without schemas, so parameters arrived as &lt;code&gt;params.random_string&lt;/code&gt;. The new server defines explicit Zod schemas:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getPage&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;Get page content by ID or title search.&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;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Page ID or title substring&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;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// query is typed as string, validated by Zod&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 AI clients proper parameter descriptions and type information, so they know exactly what to pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. One client class instead of scattered scripts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OneNoteClient&lt;/code&gt; encapsulates all Graph API operations:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OneNoteClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;notebooks&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listNotebooks&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meeting notes&lt;/span&gt;&lt;span class="dl"&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;content&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPageContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&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="c1"&gt;// HTML already converted to plain text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;3. Dependency-free HTML→text&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OneNote's Graph API returns page content as HTML. The old code used &lt;code&gt;jsdom&lt;/code&gt; (a heavy dependency) to parse it. The new &lt;code&gt;htmlToText()&lt;/code&gt; function handles it with regex — strip script/style blocks, convert block elements to newlines, decode entities, collapse whitespace:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;htmlToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;html&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;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;script|style&lt;/span&gt;&lt;span class="se"&gt;)[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/\1&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/gi&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="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;/&amp;lt;br&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\/?&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/(&lt;/span&gt;&lt;span class="sr"&gt;p|div|h&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;1-6&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|li|tr|table|ul|ol&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&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;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&amp;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="c1"&gt;// ... entity decoding, whitespace collapsing&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For the structured HTML that OneNote returns, this is more than sufficient and eliminates a large dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. stderr-only logging&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This one is subtle but critical. An MCP stdio server uses stdout exclusively for JSON-RPC messages. Any &lt;code&gt;console.log()&lt;/code&gt; call corrupts the protocol stream and crashes the connection. All logging goes through a &lt;code&gt;log()&lt;/code&gt; helper that writes to stderr:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[onenote-mcp]&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="nx"&gt;args&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;&lt;strong&gt;5. Direct OAuth 2.0 — no auth library needed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original code saved a raw access token to a file. Access tokens expire in ~1 hour, so users had to re-authenticate constantly. I initially reached for &lt;code&gt;@azure/msal-node&lt;/code&gt; to handle token persistence and silent renewal, but hit a &lt;a href="https://github.com/AzureAD/microsoft-authentication-library-for-js/issues" rel="noopener noreferrer"&gt;v5 regression&lt;/a&gt; where &lt;code&gt;acquireTokenSilent()&lt;/code&gt; sent refresh token requests without &lt;code&gt;client_id&lt;/code&gt; in the POST body — causing &lt;code&gt;AADSTS900144&lt;/code&gt; errors after the first hour.&lt;/p&gt;

&lt;p&gt;The OAuth 2.0 device-code flow is two HTTP calls. Token refresh is one. I replaced the library with ~180 lines of direct &lt;code&gt;fetch&lt;/code&gt; calls:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Silent refresh — the entire implementation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&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;Content-Type&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;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refresh_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cachedRefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&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="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&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;Tokens are cached in a simple JSON file (chmod 600, gitignored) with &lt;code&gt;access_token&lt;/code&gt;, &lt;code&gt;refresh_token&lt;/code&gt;, and &lt;code&gt;expires_at&lt;/code&gt;. When the access token expires, the server silently refreshes it using the stored refresh token — no user interaction required. Sign in once, never think about it again.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;The test suite uses Vitest with mocked Graph clients — no real API calls needed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/graph-client.js&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;createGraphClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// Mock returns controlled data&lt;/span&gt;
&lt;span class="nf"&gt;mockGraphClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/me/onenote/pages&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;value&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;p1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Meeting Notes 2024&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&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;OneNoteClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake-token&lt;/span&gt;&lt;span class="dl"&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;found&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meeting notes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;29 tests cover HTML conversion, token handling, and all OneNote client operations.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/singhAmandeep007/onenote-mcp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;onenote-mcp
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run auth     &lt;span class="c"&gt;# sign in once — tokens auto-renew after this&lt;/span&gt;
npm run verify   &lt;span class="c"&gt;# confirm it works&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add to Claude Desktop's config — this runs the TypeScript source directly via &lt;code&gt;tsx&lt;/code&gt;, no build step needed:&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;"mcpServers"&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;"onenote"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/onenote-mcp/src/mcp-server.ts"&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="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;Restart Claude Desktop to pick up the new config, then ask: &lt;em&gt;"What's in my OneNote notebooks?"&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you're building an MCP server that talks to Microsoft Graph:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use delegated, non-&lt;code&gt;.All&lt;/code&gt; scopes&lt;/strong&gt; — personal accounts silently fail with &lt;code&gt;.All&lt;/code&gt; scopes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't validate token format&lt;/strong&gt; — personal accounts return non-JWT tokens that are valid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request &lt;code&gt;offline_access&lt;/code&gt; explicitly&lt;/strong&gt; — without it you won't get a refresh token, and users re-authenticate every hour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think twice before reaching for an auth library&lt;/strong&gt; — the device-code flow is two HTTP calls; token refresh is one. Direct OAuth is fewer lines, zero dependencies, and you can read every line that runs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never write to stdout&lt;/strong&gt; — MCP uses it for JSON-RPC; all logging goes to stderr&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Zod schemas to your tools&lt;/strong&gt; — AI clients need parameter descriptions to use them correctly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full source:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/singhAmandeep007" rel="noopener noreferrer"&gt;
        singhAmandeep007
      &lt;/a&gt; / &lt;a href="https://github.com/singhAmandeep007/onenote-mcp" rel="noopener noreferrer"&gt;
        onenote-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      MCP server for Microsoft OneNote
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OneNote MCP Server&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A TypeScript &lt;a href="https://modelcontextprotocol.io" rel="nofollow noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; (MCP) server that gives AI assistants (Claude, Cursor, etc.) read/write access to your Microsoft OneNote notebooks via the Microsoft Graph API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero Azure setup required.&lt;/strong&gt; Authentication uses the &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code" rel="nofollow noopener noreferrer"&gt;device-code flow&lt;/a&gt; with a first-party Microsoft client ID, so you only need a Microsoft account. Sign in once and tokens renew automatically — you won't be asked to authenticate again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fork of &lt;a href="https://github.com/ZubeidHendricks/azure-onenote-mcp-server" rel="noopener noreferrer"&gt;azure-onenote-mcp-server&lt;/a&gt; by Zubeid Hendricks — rewritten in TypeScript with proper auth, typed tools, and a unified CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;

&lt;strong&gt;One-time authentication&lt;/strong&gt; — sign in once via device-code flow; tokens silently refresh using a stored refresh token (access tokens expire hourly, but this is invisible to you)&lt;/li&gt;

&lt;li&gt;Works with both &lt;strong&gt;personal Microsoft accounts&lt;/strong&gt; and &lt;strong&gt;work/school (Entra ID)&lt;/strong&gt; accounts&lt;/li&gt;

&lt;li&gt;List notebooks, sections, and pages&lt;/li&gt;

&lt;li&gt;Read page content as plain text (HTML is stripped automatically)&lt;/li&gt;

&lt;li&gt;Create pages with HTML content&lt;/li&gt;

&lt;li&gt;Search pages by title across all…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/singhAmandeep007/onenote-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;PRs welcome.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>typescript</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop wasting hours on React setup. 🛑

I just launched Version 2 of my production-ready React-Vite-TS boilerplate. It’s now a single-command scaffold to get you from zero to production.</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 05:41:01 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/stop-wasting-hours-on-react-setup-i-just-launched-version-2-of-my-production-ready-4kak</link>
      <guid>https://dev.to/singhamandeep007/stop-wasting-hours-on-react-setup-i-just-launched-version-2-of-my-production-ready-4kak</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/building-a-production-ready-react-vite-typescript-boilerplate-architecture-choices-dx-2i7l" class="crayons-story__hidden-navigation-link"&gt;Building a Production-Ready React + Vite + TypeScript Boilerplate: Architecture, Choices &amp;amp; DX Deep-Dive&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-3445234" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/building-a-production-ready-react-vite-typescript-boilerplate-architecture-choices-dx-2i7l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 4&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/building-a-production-ready-react-vite-typescript-boilerplate-architecture-choices-dx-2i7l" id="article-link-3445234"&gt;
          Building a Production-Ready React + Vite + TypeScript Boilerplate: Architecture, Choices &amp;amp; DX Deep-Dive
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vite"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vite&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/building-a-production-ready-react-vite-typescript-boilerplate-architecture-choices-dx-2i7l" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;11&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/building-a-production-ready-react-vite-typescript-boilerplate-architecture-choices-dx-2i7l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            12 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>From college assignment to portfolio centerpiece. 📂</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 05:28:54 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/from-college-assignment-to-portfolio-centerpiece-12ei</link>
      <guid>https://dev.to/singhamandeep007/from-college-assignment-to-portfolio-centerpiece-12ei</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd" class="crayons-story__hidden-navigation-link"&gt;Building an Interactive N-Queens Visualizer with React + TypeScript&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3455715" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd" id="article-link-3455715"&gt;
          Building an Interactive N-Queens Visualizer with React + TypeScript
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/algorithms"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;algorithms&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/visualization"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;visualization&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>algorithms</category>
      <category>react</category>
      <category>showdev</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How would you rate my portfolio out of 10 from a UX/UI and performance perspective? The good, the bad, and the ugly - tell me in the comments.</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Thu, 16 Apr 2026 05:26:05 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/how-would-you-rate-my-portfolio-out-of-10-from-a-uxui-and-performance-perspective-the-good-the-1gc8</link>
      <guid>https://dev.to/singhamandeep007/how-would-you-rate-my-portfolio-out-of-10-from-a-uxui-and-performance-perspective-the-good-the-1gc8</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba" class="crayons-story__hidden-navigation-link"&gt;How I Built My Portfolio Website with React + Vite: Themes, Particles, GitHub Visualizations, and a Dev.to-Powered Posts Page&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/singhamandeep007" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" alt="singhamandeep007 profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/singhamandeep007" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Amandeep Singh
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Amandeep Singh
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3455657" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/singhamandeep007" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F392464%2F38017c9b-89f2-42ab-b7d3-41a25728aead.jpeg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Amandeep Singh&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba" id="article-link-3455657"&gt;
          How I Built My Portfolio Website with React + Vite: Themes, Particles, GitHub Visualizations, and a Dev.to-Powered Posts Page
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/react"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;react&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/portfolio"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;portfolio&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>performance</category>
      <category>showdev</category>
      <category>ui</category>
      <category>ux</category>
    </item>
    <item>
      <title>Building an Interactive N-Queens Visualizer with React + TypeScript</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 05 Apr 2026 06:21:54 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd</link>
      <guid>https://dev.to/singhamandeep007/how-i-built-an-interactive-n-queens-visualizer-with-react-typescript-and-backtracking-4mpd</guid>
      <description>&lt;h1&gt;
  
  
  Building an Interactive N-Queens Visualizer with React + TypeScript
&lt;/h1&gt;

&lt;p&gt;I rebuilt this write-up after a full master-branch audit and focused it on what the code actually ships today: a single-page interactive algorithm visualizer with real-time constraint feedback, simulation playback, and responsive controls.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fgezjxkm4weru1yv51ign.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.amazonaws.com%2Fuploads%2Farticles%2Fgezjxkm4weru1yv51ign.jpg" alt="AI-generated hero cover: algorithmic chess visualization" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live demo: &lt;a href="https://singhAmandeep007.github.io/eight-queens-problem-visualizer/" rel="noopener noreferrer"&gt;https://singhAmandeep007.github.io/eight-queens-problem-visualizer/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/singhAmandeep007/eight-queens-problem-visualizer" rel="noopener noreferrer"&gt;https://github.com/singhAmandeep007/eight-queens-problem-visualizer&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What the App Does&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;Design System&lt;/li&gt;
&lt;li&gt;Theming&lt;/li&gt;
&lt;li&gt;Motion and Animations&lt;/li&gt;
&lt;li&gt;Particles and Background Effects&lt;/li&gt;
&lt;li&gt;Special Interaction&lt;/li&gt;
&lt;li&gt;Projects Module&lt;/li&gt;
&lt;li&gt;Posts and Blog Module&lt;/li&gt;
&lt;li&gt;About and Profile Module&lt;/li&gt;
&lt;li&gt;Responsiveness&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Key Implementation Details&lt;/li&gt;
&lt;li&gt;Lessons Learned and Next Steps&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The N-Queens puzzle asks us to place N pieces on an N x N board with zero conflicts.&lt;/p&gt;

&lt;p&gt;This project turns that into an interactive engineering playground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Piece-rule switching: queen, bishop, rook, knight&lt;/li&gt;
&lt;li&gt;Manual mode for direct placement and conflict feedback&lt;/li&gt;
&lt;li&gt;Simulation mode for animated backtracking exploration&lt;/li&gt;
&lt;li&gt;Variable board size from 4x4 to 8x8&lt;/li&gt;
&lt;li&gt;Solution list replay to inspect discovered arrangements&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F080cu09zushgg4lfn3jq.png" 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.amazonaws.com%2Fuploads%2Farticles%2F080cu09zushgg4lfn3jq.png" alt="Home and board overview" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The app is intentionally compact but still modular in behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App shell in App.tsx&lt;/li&gt;
&lt;li&gt;State domains in ControlContext and AlertContext&lt;/li&gt;
&lt;li&gt;Algorithm and rendering orchestration in Chessboard&lt;/li&gt;
&lt;li&gt;Rule engine utilities in constants&lt;/li&gt;
&lt;li&gt;Reusable control primitives in ControlSelect and common/button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flowchart image (static for Dev.to compatibility):&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fxfqb6o0vy5yot9u1mj6a.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fxfqb6o0vy5yot9u1mj6a.png" alt="Architecture flowchart" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Design System
&lt;/h2&gt;

&lt;p&gt;The design system is lightweight and practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS variables define core color tokens&lt;/li&gt;
&lt;li&gt;Styled-components handle composable UI surfaces&lt;/li&gt;
&lt;li&gt;Reusable button primitive keeps action affordances consistent&lt;/li&gt;
&lt;li&gt;Board and controls share clear visual hierarchy and spacing&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Ffzclilgg2kwobzhbhi9c.png" 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.amazonaws.com%2Fuploads%2Farticles%2Ffzclilgg2kwobzhbhi9c.png" alt="Control configuration panel" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Theming
&lt;/h2&gt;

&lt;p&gt;Even without a dark/light toggle, the app is tokenized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primary and secondary hues drive control and action zones&lt;/li&gt;
&lt;li&gt;Surface and background tokens support layered feedback&lt;/li&gt;
&lt;li&gt;Piece and board contrast remains readable across interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because tokens are centralized, runtime theme switching can be introduced later with minimal component churn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motion and Animations
&lt;/h2&gt;

&lt;p&gt;Motion is used to explain logic rather than decorate UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simulation playback updates board state at user-selected speed&lt;/li&gt;
&lt;li&gt;Alerts animate in and out for lightweight status communication&lt;/li&gt;
&lt;li&gt;Solved-state celebration gives immediate completion feedback&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fpkdt2jou988c5vzswmu1.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fpkdt2jou988c5vzswmu1.png" alt="Simulation running state" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Particles and Background Effects
&lt;/h2&gt;

&lt;p&gt;This branch intentionally avoids heavy particle layers and uses informative effects instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conflict squares use striped overlays&lt;/li&gt;
&lt;li&gt;Board container shadows emphasize focus&lt;/li&gt;
&lt;li&gt;Celebration overlay marks solved states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the experience fast while preserving strong visual state signaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Special Interaction
&lt;/h2&gt;

&lt;p&gt;The standout interaction is simulation control + replayability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Play/Stop orchestrates async search&lt;/li&gt;
&lt;li&gt;Simulation auto-interrupt logic handles tab visibility changes&lt;/li&gt;
&lt;li&gt;Solved boards can be reset and replayed rapidly&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;GIF fallback:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fz34hntvwht8mpandpizk.gif" 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.amazonaws.com%2Fuploads%2Farticles%2Fz34hntvwht8mpandpizk.gif" alt="Simulation recording (GIF fallback)" width="960" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Projects Module
&lt;/h2&gt;

&lt;p&gt;In this single-page architecture, the solution explorer acts as the project/results module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every valid solution is persisted&lt;/li&gt;
&lt;li&gt;Entries are clickable to preview placements instantly&lt;/li&gt;
&lt;li&gt;Duplicate solutions are deduplicated using serialized keys&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fa63qvjpvc9uk7asvfosc.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fa63qvjpvc9uk7asvfosc.png" alt="Solution explorer panel" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Posts and Blog Module
&lt;/h2&gt;

&lt;p&gt;The in-app information modal works as the educational content module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Problem statement and interaction model are available inline&lt;/li&gt;
&lt;li&gt;Portal rendering avoids z-index and stacking issues&lt;/li&gt;
&lt;li&gt;Users can enter and exit context quickly without navigation&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F2ro22gzqa43hks1by7w5.png" 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.amazonaws.com%2Fuploads%2Farticles%2F2ro22gzqa43hks1by7w5.png" alt="In-app information modal" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About and Profile Module
&lt;/h2&gt;

&lt;p&gt;Profile attribution is implemented in the footer module:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author identity is visible in the app shell&lt;/li&gt;
&lt;li&gt;External profile link supports discoverability and ownership&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fso0fr5eofuhncazsm4jy.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fso0fr5eofuhncazsm4jy.png" alt="Profile footer module" width="800" height="1111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsiveness
&lt;/h2&gt;

&lt;p&gt;Responsive behavior is handled through global breakpoints and adaptive layouts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Root font scaling across screen-size bands&lt;/li&gt;
&lt;li&gt;Control bar wrapping under smaller widths&lt;/li&gt;
&lt;li&gt;Chessboard + side panel stack behavior for mobile ergonomics&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fz8ldaxjcxovuwyazf1de.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fz8ldaxjcxovuwyazf1de.png" alt="Mobile responsive layout" width="390" height="844"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Performance decisions visible in the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useMemo for board-grid generation by board size&lt;/li&gt;
&lt;li&gt;Cached solved-state checks for repeated position sets&lt;/li&gt;
&lt;li&gt;Fast simulation mode for rapid enumeration&lt;/li&gt;
&lt;li&gt;Input controls disabled while simulation runs to reduce race conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Known trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Position encoding uses row*10+col, which is compact and efficient for current board ranges (4-8), but not ideal for larger generalized boards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Deployment pipeline is straightforward and production-safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;npm run build creates dist output&lt;/li&gt;
&lt;li&gt;npm run deploy uses gh-pages&lt;/li&gt;
&lt;li&gt;Vite base path is derived from GITHUB_PAGES_REPO for GitHub Pages correctness&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Implementation Details
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Context-driven state architecture&lt;/li&gt;
&lt;li&gt;ControlContext owns mode, speed, piece type, board size, simulation state&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AlertContext centralizes user feedback and timeout cleanup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rule engine abstraction&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;checkConflictMethods encapsulates row/column, diagonal, and knight checks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;checkIsAttacking and checkIsSolved compose these rules for both manual and simulation paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Async simulation loop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recursive backtracking-style search enumerates candidate positions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Board state updates are interleaved with delays for visual progression&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stop conditions are managed with refs and side effects for safe interruption&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lessons Learned and Next Steps
&lt;/h2&gt;

&lt;p&gt;Lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicit state boundaries make even single-page apps easier to reason about&lt;/li&gt;
&lt;li&gt;Algorithm visualizers benefit from controls that expose execution speed and mode&lt;/li&gt;
&lt;li&gt;Visual effects should carry semantic meaning, not just aesthetics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next improvements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract solver logic to a dedicated module for stronger unit testing&lt;/li&gt;
&lt;li&gt;Add symmetry reduction for mathematically equivalent solutions&lt;/li&gt;
&lt;li&gt;Move from row*10+col encoding to coordinate tuples for long-term extensibility&lt;/li&gt;
&lt;li&gt;Add keyboard-first interaction flow for accessibility&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>algorithms</category>
      <category>visualization</category>
    </item>
    <item>
      <title>How I Built My Portfolio Website with React + Vite: Themes, Particles, GitHub Visualizations, and a Dev.to-Powered Posts Page</title>
      <dc:creator>Amandeep Singh</dc:creator>
      <pubDate>Sun, 05 Apr 2026 06:00:06 +0000</pubDate>
      <link>https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba</link>
      <guid>https://dev.to/singhamandeep007/how-i-built-my-portfolio-website-with-react-vite-themes-particles-github-visualizations-and-a-37ba</guid>
      <description>&lt;h1&gt;
  
  
  How I Built My Portfolio Website with React + Vite: Themes, Particles, GitHub Visualizations, and a Dev.to-Powered Posts Page
&lt;/h1&gt;

&lt;p&gt;I recently rebuilt my personal portfolio to reflect how I like to build products: clear architecture, expressive UI, strong performance defaults, and data-backed sections that stay fresh.&lt;/p&gt;

&lt;p&gt;Live website: &lt;a href="https://singhamandeep007.github.io/" rel="noopener noreferrer"&gt;https://singhamandeep007.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/singhAmandeep007/singhAmandeep007.github.io" rel="noopener noreferrer"&gt;https://github.com/singhAmandeep007/singhAmandeep007.github.io&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fut6c0bsk8tk2ps9hg31z.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fut6c0bsk8tk2ps9hg31z.png" alt="Portfolio Home" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project Overview&lt;/li&gt;
&lt;li&gt;Design Direction&lt;/li&gt;
&lt;li&gt;Architecture At A Glance&lt;/li&gt;
&lt;li&gt;Theme Engine (Multi Theme)&lt;/li&gt;
&lt;li&gt;Particles Background With Floating Tech Logos&lt;/li&gt;
&lt;li&gt;Resume Open Animation&lt;/li&gt;
&lt;li&gt;Projects Page&lt;/li&gt;
&lt;li&gt;Posts Page (Powered By Dev.to API)&lt;/li&gt;
&lt;li&gt;About Page + GitHub Contributions + GitHub Showcase&lt;/li&gt;
&lt;li&gt;Responsive UX + Sidebar Navigation&lt;/li&gt;
&lt;li&gt;Performance + Developer Experience&lt;/li&gt;
&lt;li&gt;What I Would Improve Next&lt;/li&gt;
&lt;li&gt;Final Thoughts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This portfolio is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React 18 + TypeScript&lt;/li&gt;
&lt;li&gt;Vite 5&lt;/li&gt;
&lt;li&gt;styled-components&lt;/li&gt;
&lt;li&gt;React Router (lazy route loading)&lt;/li&gt;
&lt;li&gt;tsParticles for animated background motion&lt;/li&gt;
&lt;li&gt;Dev.to API integration for articles&lt;/li&gt;
&lt;li&gt;GitHub GraphQL API integration for contribution and profile showcase widgets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the UI visually alive without becoming noisy.&lt;/li&gt;
&lt;li&gt;Keep content dynamic where possible (GitHub + Dev.to).&lt;/li&gt;
&lt;li&gt;Keep the codebase modular and easy to personalize.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design Direction
&lt;/h2&gt;

&lt;p&gt;I wanted the experience to feel like a blend of developer identity and product polish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bright accent colors that can switch at runtime,&lt;/li&gt;
&lt;li&gt;subtle glassmorphism where it helps depth,&lt;/li&gt;
&lt;li&gt;playful details (handwave animation, envelope-style resume interaction),&lt;/li&gt;
&lt;li&gt;high-density information modules on About (contributions, profile metrics, repo cards).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole layout stays section-based, with each page having a clear visual job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Home: first impression + CTA&lt;/li&gt;
&lt;li&gt;Projects: proof of work&lt;/li&gt;
&lt;li&gt;Posts: writing and thought process&lt;/li&gt;
&lt;li&gt;About: profile depth + GitHub signal&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture At A Glance
&lt;/h2&gt;

&lt;p&gt;I use a shared layout route that always renders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sidebar menu,&lt;/li&gt;
&lt;li&gt;active route outlet,&lt;/li&gt;
&lt;li&gt;theme toggler,&lt;/li&gt;
&lt;li&gt;particle background.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A[ThemeProvider + GlobalStyle] --&amp;gt; B[Shared Layout]
  B --&amp;gt; C[SidebarMenu]
  B --&amp;gt; D[Route Outlet]
  B --&amp;gt; E[ToggleTheme]
  B --&amp;gt; F[ParticlesContainer]

  D --&amp;gt; H[Home]
  D --&amp;gt; I[Projects]
  D --&amp;gt; J[Posts]
  D --&amp;gt; K[About]

  K --&amp;gt; L[GitHub Contributions GraphQL]
  K --&amp;gt; M[GitHub Showcase GraphQL]
  J --&amp;gt; N[Dev.to Articles API]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure keeps global behavior centralized while pages remain focused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theme Engine (Multi Theme)
&lt;/h2&gt;

&lt;p&gt;One of my favorite parts is the theme system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Themes are created through a small theme factory (&lt;code&gt;createTheme&lt;/code&gt;) using primary/background/font values.&lt;/li&gt;
&lt;li&gt;The app generates CSS variables for each theme class.&lt;/li&gt;
&lt;li&gt;Theme switching is class-based on &lt;code&gt;document.documentElement&lt;/code&gt; for instant variable swapping.&lt;/li&gt;
&lt;li&gt;A small transition class smooths the color shift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also map theme colors into GitHub contribution chart palettes, so the data visuals feel native to whichever theme is active.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fippykg58vfzxcttw25op.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fippykg58vfzxcttw25op.png" alt="Theme Toggle Open" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Particles Background With Floating Tech Logos
&lt;/h2&gt;

&lt;p&gt;The background uses tsParticles in two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;baseline moving particles,&lt;/li&gt;
&lt;li&gt;floating image particles sourced from my tech logo list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A batched loader progressively injects logo particles so the effect ramps up instead of appearing all at once. It gives the page ambient motion while keeping content readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resume Open Animation
&lt;/h2&gt;

&lt;p&gt;Instead of a normal download button, I built a small envelope interaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a cover flap rotates with &lt;code&gt;rotateX&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;the letter slides out,&lt;/li&gt;
&lt;li&gt;click opens/closes,&lt;/li&gt;
&lt;li&gt;click on letter triggers resume download.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is intentionally playful, but still functional and accessible.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fasd04hv2n1my1o5h8k79.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fasd04hv2n1my1o5h8k79.png" alt="Resume Animation State" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Projects Page
&lt;/h2&gt;

&lt;p&gt;The Projects page is data-driven from a single projects data source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;markdown-supported descriptions,&lt;/li&gt;
&lt;li&gt;per-project stack icons,&lt;/li&gt;
&lt;li&gt;image/video slide support,&lt;/li&gt;
&lt;li&gt;fullscreen preview support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I alternate layout direction between odd/even project items for visual rhythm on larger screens, then collapse to a single-column flow on mobile.&lt;/p&gt;

&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%2Fres.cloudinary.com%2Fdryiuvv1l%2Fimage%2Fupload%2Fv1775368708%2Fportfolio-blog%2Fprojects.png" 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%2Fres.cloudinary.com%2Fdryiuvv1l%2Fimage%2Fupload%2Fv1775368708%2Fportfolio-blog%2Fprojects.png" alt="Projects Page" width="800" height="3424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Posts Page (Powered By Dev.to API)
&lt;/h2&gt;

&lt;p&gt;The Posts page fetches articles directly from my Dev.to account and renders them as cards with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cover image,&lt;/li&gt;
&lt;li&gt;tags,&lt;/li&gt;
&lt;li&gt;publish date,&lt;/li&gt;
&lt;li&gt;comments count,&lt;/li&gt;
&lt;li&gt;reactions count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I filter out repost-like items by checking reading time so the page emphasizes full technical pieces.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fnm1aoahciptq7q9plqzr.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fnm1aoahciptq7q9plqzr.png" alt="Posts Page" width="800" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About Page + GitHub Contributions + GitHub Showcase
&lt;/h2&gt;

&lt;p&gt;This page combines static profile context with live GitHub data.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Contributions Calendar
&lt;/h3&gt;

&lt;p&gt;I fetch contribution years and calendars from GitHub GraphQL, then render a custom SVG chart.&lt;/p&gt;

&lt;p&gt;Features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;virtualized week rendering for smoother scroll,&lt;/li&gt;
&lt;li&gt;multiple visual modes (Heatmap, Forest, Garden, Skyline),&lt;/li&gt;
&lt;li&gt;year jump controls,&lt;/li&gt;
&lt;li&gt;auto-scroll playback,&lt;/li&gt;
&lt;li&gt;palette adaptation by active theme.&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fkete0v1lsojr216pijrs.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fkete0v1lsojr216pijrs.png" alt="Contributions Forest Theme" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) GitHub Showcase
&lt;/h3&gt;

&lt;p&gt;I also built a profile showcase module using GraphQL to display:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;follower and repo metrics,&lt;/li&gt;
&lt;li&gt;contribution metrics (commits/PRs),&lt;/li&gt;
&lt;li&gt;language distribution bar,&lt;/li&gt;
&lt;li&gt;pinned/top repositories with topic badges.&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fpb9uxoa0osfrvljr5vqi.png" 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.amazonaws.com%2Fuploads%2Farticles%2Fpb9uxoa0osfrvljr5vqi.png" alt="GitHub Showcase Section" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full About page context:&lt;/p&gt;

&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%2Fres.cloudinary.com%2Fdryiuvv1l%2Fimage%2Fupload%2Fv1775368694%2Fportfolio-blog%2Fabout.png" 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%2Fres.cloudinary.com%2Fdryiuvv1l%2Fimage%2Fupload%2Fv1775368694%2Fportfolio-blog%2Fabout.png" alt="About Page" width="800" height="1404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsive UX + Sidebar Navigation
&lt;/h2&gt;

&lt;p&gt;Responsiveness is handled through centralized breakpoints and component-level media rules.&lt;/p&gt;

&lt;p&gt;Key UX behaviors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;focus-trapped sidebar menu,&lt;/li&gt;
&lt;li&gt;click-outside to close overlays,&lt;/li&gt;
&lt;li&gt;blur treatment on background content when overlays are active,&lt;/li&gt;
&lt;li&gt;typography scaling by breakpoint,&lt;/li&gt;
&lt;li&gt;horizontal overflow handling for dense visual modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps keyboard and touch interactions practical across sizes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance + Developer Experience
&lt;/h2&gt;

&lt;p&gt;Some implementation decisions that helped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;route-level lazy imports,&lt;/li&gt;
&lt;li&gt;manual chunk grouping for vendor and particle libraries,&lt;/li&gt;
&lt;li&gt;optional dev-time TypeScript/ESLint checking via environment toggles,&lt;/li&gt;
&lt;li&gt;build visualizer output for bundle inspection,&lt;/li&gt;
&lt;li&gt;strict TypeScript config and lint/format scripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For deployment, GitHub Actions builds and ships to GitHub Pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Would Improve Next
&lt;/h2&gt;

&lt;p&gt;If I take this further, I would like to add:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;richer skeleton/loading states instead of blank transitions,&lt;/li&gt;
&lt;li&gt;offline support + cache strategy for API-backed sections,&lt;/li&gt;
&lt;li&gt;more accessibility audits (focus indicators, reduced-motion pathways),&lt;/li&gt;
&lt;li&gt;analytics on which projects/posts get most engagement.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;This portfolio became more than a profile page. It is now a living frontend playground where I can experiment with UI systems, data visualizations, and interaction patterns in production.&lt;/p&gt;

&lt;p&gt;If you want to explore or fork it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live: &lt;a href="https://singhamandeep007.github.io/" rel="noopener noreferrer"&gt;https://singhamandeep007.github.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/singhAmandeep007/singhAmandeep007.github.io" rel="noopener noreferrer"&gt;https://github.com/singhAmandeep007/singhAmandeep007.github.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would love feedback on the architecture and UX decisions.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>portfolio</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
