<?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: Viram Choksi</title>
    <description>The latest articles on DEV Community by Viram Choksi (@viramchoksi).</description>
    <link>https://dev.to/viramchoksi</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%2F3849761%2Fa6669f11-accd-4b97-8eac-77dd8f166043.png</url>
      <title>DEV Community: Viram Choksi</title>
      <link>https://dev.to/viramchoksi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viramchoksi"/>
    <language>en</language>
    <item>
      <title>Nobody teaches you to debug</title>
      <dc:creator>Viram Choksi</dc:creator>
      <pubDate>Tue, 04 Aug 2026 18:42:49 +0000</pubDate>
      <link>https://dev.to/viramchoksi/nobody-teaches-you-to-debug-39om</link>
      <guid>https://dev.to/viramchoksi/nobody-teaches-you-to-debug-39om</guid>
      <description>&lt;p&gt;You've been here.&lt;/p&gt;

&lt;p&gt;The code looks right. It runs. The output is wrong.&lt;/p&gt;

&lt;p&gt;No error. No stack trace. Nothing to paste into Google. Just a number that should&lt;br&gt;
be 10 and is 4, and no idea where it went.&lt;/p&gt;


&lt;h2&gt;
  
  
  Three years of writing code, zero of fixing it
&lt;/h2&gt;

&lt;p&gt;Every assignment I was given was graded on writing something from scratch.&lt;/p&gt;

&lt;p&gt;Not one was graded on fixing something broken.&lt;/p&gt;

&lt;p&gt;Then you get to real work and it inverts completely. You spend far more time&lt;br&gt;
reading code you didn't write and finding out why it misbehaves than you ever&lt;br&gt;
spend on a blank file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We're trained for the rare case and left to improvise the common one.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Bugs aren't infinite. They're a handful of shapes
&lt;/h2&gt;

&lt;p&gt;This is the thing that changed how I think about it.&lt;/p&gt;

&lt;p&gt;Almost every bug is one of a small number of patterns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;the shape&lt;/th&gt;
&lt;th&gt;what you see&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🔁 Loop runs one time too many&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;IndexError&lt;/code&gt; on the last item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0️⃣ A valid &lt;code&gt;0&lt;/code&gt; treated as missing&lt;/td&gt;
&lt;td&gt;Setting it to zero has no effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔒 Closure captures the variable, not the value&lt;/td&gt;
&lt;td&gt;Every callback reports the last one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📋 A "copy" that's the same object&lt;/td&gt;
&lt;td&gt;Editing the copy changes the original&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you can recognise the shape, you stop debugging line by line and start&lt;br&gt;
recognising. &lt;strong&gt;The next one takes minutes instead of an afternoon.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't get that from reading. You get it from finding bugs, repeatedly, and&lt;br&gt;
being told what you just found.&lt;/p&gt;


&lt;h2&gt;
  
  
  So I built the practice
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://trybughunt.vercel.app" rel="noopener noreferrer"&gt;BugHunt&lt;/a&gt; gives you working code with exactly one&lt;br&gt;
bug in it.&lt;/p&gt;

&lt;p&gt;You get the &lt;strong&gt;symptom&lt;/strong&gt;, not the cause:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;last_char("hello")&lt;/code&gt; raises &lt;code&gt;IndexError: string index out of range&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;last_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;     &lt;span class="c1"&gt;# ← one character too far
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You find it. Fix it in the browser. Run it against the test cases.&lt;/p&gt;

&lt;p&gt;When it passes, you get an explanation of the &lt;strong&gt;pattern&lt;/strong&gt; — not just the diff.&lt;br&gt;
Knowing this loop needed &lt;code&gt;i + 1&lt;/code&gt; is nearly worthless. Recognising an off-by-one&lt;br&gt;
on sight is worth a lot.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few things that matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nothing to install.&lt;/strong&gt; Python runs through WebAssembly right in the tab. Works on&lt;br&gt;
a locked-down college lab machine with no admin rights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No account needed.&lt;/strong&gt; Solve a bug, read the explanation, close the tab. An account&lt;br&gt;
only saves your progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch it run.&lt;/strong&gt; Step through line by line and see every variable at every step.&lt;br&gt;
Seeing the exact moment a value goes wrong is very different from being told&lt;br&gt;
where the bug was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free.&lt;/strong&gt; No ads, no paywall. It runs on free hosting tiers on purpose — there's&lt;br&gt;
nothing to recoup, so there's no reason to start charging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try one
&lt;/h2&gt;

&lt;p&gt;Pick a bug and see how long it takes you:&lt;/p&gt;

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

&lt;p&gt;If an explanation is confusing, there's a report button on every challenge. I&lt;br&gt;
read them.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>python</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Four things that surprised me running Python in the browser</title>
      <dc:creator>Viram Choksi</dc:creator>
      <pubDate>Tue, 04 Aug 2026 18:31:20 +0000</pubDate>
      <link>https://dev.to/viramchoksi/four-things-that-surprised-me-running-python-in-the-browser-4lci</link>
      <guid>https://dev.to/viramchoksi/four-things-that-surprised-me-running-python-in-the-browser-4lci</guid>
      <description>&lt;p&gt;I built a debugging-practice site where student code runs &lt;strong&gt;entirely in the&lt;br&gt;
browser&lt;/strong&gt;. Python via &lt;a href="https://pyodide.org" rel="noopener noreferrer"&gt;Pyodide&lt;/a&gt;, JavaScript in a worker. No&lt;br&gt;
server executes anything.&lt;/p&gt;

&lt;p&gt;No execution bill, no queue, no sandbox to maintain. But four things bit me hard.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Your arguments aren't Python objects
&lt;/h2&gt;

&lt;p&gt;Pass a JS object into Python and you get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: 'pyodide.ffi.JsProxy' object is not subscriptable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not a &lt;code&gt;dict&lt;/code&gt;. It's a live view of the JS object, and it supports neither&lt;br&gt;
&lt;code&gt;obj[key]&lt;/code&gt; nor &lt;code&gt;.get()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Convert explicitly:&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;pyArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pyodide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;pyArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. &lt;code&gt;null&lt;/code&gt; is not &lt;code&gt;None&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This one passed my entire test suite while being broken in production.&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="nx"&gt;pyodide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;check&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type(v)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JsNull&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bool(v)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ falsy, as expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v is None&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;False&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌ the surprise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It's falsy, so truthiness checks work fine. But &lt;code&gt;is None&lt;/code&gt; fails — which was&lt;br&gt;
exactly what my code was checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why my tests missed it:&lt;/strong&gt; the harness used &lt;code&gt;json.loads&lt;/code&gt;. The app used &lt;code&gt;toPy&lt;/code&gt;.&lt;br&gt;
Different conversion paths, different answers.&lt;/p&gt;

&lt;p&gt;If you need a real &lt;code&gt;None&lt;/code&gt;, create it &lt;em&gt;in&lt;/em&gt; Python. Don't pass one across.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. &lt;code&gt;sys.settrace&lt;/code&gt; is a free step debugger
&lt;/h2&gt;

&lt;p&gt;Want to show users their code running line by line? Python basically hands it to you:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f_code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;co_name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;                    &lt;span class="c1"&gt;# skip library frames
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f_lineno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f_locals&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_tracer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things this naive version gets wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add a step cap.&lt;/strong&gt; A tight loop generates steps faster than it burns a 5-second
timeout. You need both guards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle &lt;code&gt;exception&lt;/code&gt;.&lt;/strong&gt; During unwinding, the &lt;code&gt;return&lt;/code&gt; event still fires with
&lt;code&gt;arg=None&lt;/code&gt;. Miss it and your trace says "returned None" for code that crashed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Your snapshots are lying
&lt;/h2&gt;

&lt;p&gt;A user screenshot exposed this one.&lt;/p&gt;

&lt;p&gt;Every step in the trace showed the &lt;strong&gt;final&lt;/strong&gt; state of a list. Step 1 included&lt;br&gt;
mutations that hadn't happened yet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;  tracing:  nums = []; nums.append(1); nums.append(2)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;- what the trace showed        + what actually happened
- step 1   nums = [1, 2]       + step 1   nums = []
- step 2   nums = [1, 2]       + step 2   nums = [1]
- step 3   nums = [1, 2]       + step 3   nums = [1, 2]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;frame.f_locals&lt;/code&gt; gives you &lt;strong&gt;references&lt;/strong&gt;. Snapshot a list and you've stored a&lt;br&gt;
pointer to something the program keeps mutating.&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;# copy at capture time
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Any time you snapshot mutable state over time, you're one reference away from&lt;br&gt;
a history that rewrites itself.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Worth it?
&lt;/h2&gt;

&lt;p&gt;Yes. Zero execution cost, scales infinitely, works on a locked-down lab machine&lt;br&gt;
with nothing installed.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;test against the real runtime.&lt;/strong&gt; Every bug above was found by running the&lt;br&gt;
actual thing — not by unit tests around it. The &lt;code&gt;JsNull&lt;/code&gt; one passed a fully green&lt;br&gt;
suite.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for &lt;a href="https://trybughunt.vercel.app" rel="noopener noreferrer"&gt;BugHunt&lt;/a&gt; — free debugging practice,&lt;br&gt;
runs in your browser, no account needed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why My Portfolio Has a Boot Sequence, Window Manager, and 6 Playable Games</title>
      <dc:creator>Viram Choksi</dc:creator>
      <pubDate>Sun, 29 Mar 2026 17:19:36 +0000</pubDate>
      <link>https://dev.to/viramchoksi/why-my-portfolio-has-a-boot-sequence-window-manager-and-6-playable-games-15j9</link>
      <guid>https://dev.to/viramchoksi/why-my-portfolio-has-a-boot-sequence-window-manager-and-6-playable-games-15j9</guid>
      <description>&lt;h1&gt;
  
  
  I Built a Full Operating System as My Developer Portfolio — Here's How
&lt;/h1&gt;

&lt;p&gt;Most developer portfolios look the same: a hero section, some cards, a contact form. I wanted mine to make recruiters stop scrolling.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;ViramOS&lt;/strong&gt; — a fully functional desktop operating system that runs in the browser. Boot sequence, login screen, draggable windows, taskbar, 13+ interactive apps, 6 playable games, and a working terminal. All built with Next.js, React, and Framer Motion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://viram-choksi.vercel.app" rel="noopener noreferrer"&gt;viram-choksi.vercel.app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does It Actually Do?
&lt;/h2&gt;

&lt;p&gt;When you visit my portfolio, you don't see a webpage. You see this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Boot Sequence&lt;/strong&gt; — Matrix rain, ASCII art, fake BIOS logs loading frameworks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login Screen&lt;/strong&gt; — Glassmorphic card with animated gradient ring, press Enter to unlock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desktop&lt;/strong&gt; — Animated wallpapers, floating particles, draggable app icons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window Manager&lt;/strong&gt; — Drag, resize, minimize, maximize, snap-to-edge (like Windows 11)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Taskbar&lt;/strong&gt; — Running app indicators, system tray, control center, clock&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And then 13+ apps you can actually use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terminal&lt;/strong&gt; with 15+ commands (&lt;code&gt;whoami&lt;/code&gt;, &lt;code&gt;neofetch&lt;/code&gt;, &lt;code&gt;benchmark&lt;/code&gt;, &lt;code&gt;architecture&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projects&lt;/strong&gt; file manager with metrics and impact numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; package manager with proficiency bars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games&lt;/strong&gt; — Snake, 2048, Tic Tac Toe (with minimax AI), Typing Test, Memory Card, Reaction Time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System Monitor&lt;/strong&gt; — Real-time FPS, Core Web Vitals, bundle size breakdown, architecture decision records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt; — 12 wallpapers, accent colors, font scaling, night shift, high contrast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command Palette&lt;/strong&gt; — Press &lt;code&gt;/&lt;/code&gt; to fuzzy-search and launch any app&lt;/li&gt;
&lt;/ul&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Next.js 16 (App Router, SSR, ISR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;React 19, Framer Motion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind CSS 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3D&lt;/td&gt;
&lt;td&gt;Three.js + React Three Fiber&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;React Context (no Redux needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Nodemailer via API route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;Vercel Analytics + Speed Insights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;Vercel (Edge Network)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Architecture Decisions That Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why React Context over Redux?
&lt;/h3&gt;

&lt;p&gt;The OS state (open windows, settings, theme) is shared across components but updates infrequently — user clicks, not real-time data. Context + &lt;code&gt;useCallback&lt;/code&gt;/&lt;code&gt;useMemo&lt;/code&gt; keeps the bundle smaller and API simpler. Redux would add ~12KB gzipped for state that changes maybe 5 times per session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why DOM Windows, Not Canvas?
&lt;/h3&gt;

&lt;p&gt;I considered building the window manager on Canvas (like Figma does). But DOM-based windows mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native text selection inside windows&lt;/li&gt;
&lt;li&gt;Form inputs work naturally&lt;/li&gt;
&lt;li&gt;Screen readers can access content&lt;/li&gt;
&lt;li&gt;Standard React component composition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Canvas would give better performance at 50+ simultaneous windows, but a portfolio never has more than 5 open.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Framer Motion over CSS Animations?
&lt;/h3&gt;

&lt;p&gt;The boot sequence, window transitions, and desktop effects need &lt;strong&gt;orchestrated animations with exit transitions&lt;/strong&gt;. CSS can't do &lt;code&gt;AnimatePresence&lt;/code&gt; — you can't animate an element leaving the DOM. Framer Motion costs ~45KB gzipped but enables spring physics, gesture-based drag, and coordinated mount/unmount animations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Window Snap Zones
&lt;/h3&gt;

&lt;p&gt;I implemented Windows 11-style snap zones: drag a window to the left edge and it snaps to the left half, right edge for right half, top for maximize. The snap preview shows a translucent overlay while dragging near edges. Dragging a snapped window out restores it to its original size.&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;getSnapZone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientY&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// maximize&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// left half&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;vw&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// right half&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Settings Persistence
&lt;/h3&gt;

&lt;p&gt;All user preferences (wallpaper, accent color, font size, night shift) persist to &lt;code&gt;localStorage&lt;/code&gt;. The trick with Next.js SSR: you can't read &lt;code&gt;localStorage&lt;/code&gt; during server render or you get hydration mismatches. So I start with defaults, then restore from &lt;code&gt;localStorage&lt;/code&gt; in a &lt;code&gt;useEffect&lt;/code&gt; after mount.&lt;/p&gt;




&lt;h2&gt;
  
  
  SEO for a JavaScript-Heavy Portfolio
&lt;/h2&gt;

&lt;p&gt;The biggest SEO challenge: the homepage is entirely JS-rendered. Google can crawl dynamic content, but it takes extra time. My approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sr-only&lt;/code&gt; content&lt;/strong&gt; — Full text resume hidden but crawlable (same content as the interactive apps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; fallback&lt;/strong&gt; — Visible content for JS-disabled users and crawlers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11 static landing pages&lt;/strong&gt; — &lt;code&gt;/frontend-developer-ahmedabad&lt;/code&gt;, &lt;code&gt;/react-developer-ahmedabad&lt;/code&gt;, etc. with visible content, FAQ schema, and breadcrumbs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-LD structured data&lt;/strong&gt; — 7 schemas: Person, FAQPage, WebSite, ProfessionalService, ItemList, LocalBusiness, Organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic sitemap&lt;/strong&gt; with proper priorities and dates&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Accessibility in an OS Simulation
&lt;/h2&gt;

&lt;p&gt;Building an accessible OS simulation sounds contradictory, but I took it seriously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;prefers-reduced-motion&lt;/code&gt;&lt;/strong&gt; — All CSS animations respect the user preference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;role="application"&lt;/code&gt;&lt;/strong&gt; on the desktop, &lt;code&gt;role="dialog"&lt;/code&gt; on windows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip link&lt;/strong&gt; — Tab-accessible "Skip to desktop" link&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard navigation&lt;/strong&gt; — Tab through desktop icons, Enter/Space to open apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aria-label&lt;/code&gt;&lt;/strong&gt; on all interactive elements (window controls, taskbar, context menu)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus indicators&lt;/strong&gt; — Visible &lt;code&gt;focus-visible&lt;/code&gt; rings on all buttons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic HTML&lt;/strong&gt; — &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; instead of &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WCAG compliance in my day job went from 40% to 95%. I applied the same thinking here.&lt;/p&gt;




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

&lt;p&gt;Despite the visual complexity, the portfolio loads fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code splitting&lt;/strong&gt; — Each app component only loads when its window opens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutable cache headers&lt;/strong&gt; — 1-year cache for JS/CSS assets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive particles&lt;/strong&gt; — Fewer particles on mobile for 60fps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy window rendering&lt;/strong&gt; — Zero hidden DOM nodes for closed apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel Edge Network&lt;/strong&gt; — CDN distribution + SSR&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The OS metaphor forces you to solve real problems&lt;/strong&gt; — Window z-index management, focus trapping, state persistence, responsive layouts for different "screen sizes" inside windows. These are the same problems production apps face.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints breed creativity&lt;/strong&gt; — A scrollable page is easy. An interactive desktop with overlapping windows, a taskbar, and 13 apps forces you to think about layout, performance, and UX simultaneously.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recruiters spend 15-30 seconds&lt;/strong&gt; — If they don't instantly see something interesting, they leave. The boot sequence animation buys you 5 seconds of attention. The desktop experience keeps them exploring.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

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

&lt;p&gt;Open the Terminal and type &lt;code&gt;help&lt;/code&gt;. Try &lt;code&gt;/&lt;/code&gt; to open the Command Palette. Play Snake. Change the wallpaper in Settings. Drag a window to the edge and watch it snap.&lt;/p&gt;

&lt;p&gt;If you're building your own portfolio, steal the architecture — just don't copy the design. The world needs more creative portfolios and fewer Bootstrap templates.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Viram Choksi, a Frontend Engineer in Ahmedabad, India. I build with React.js, Next.js, Vue.js, and TypeScript. Currently at Motadata, previously at MindInventory. Say hi at &lt;a href="mailto:viramchoksi77166@gmail.com"&gt;viramchoksi77166@gmail.com&lt;/a&gt; or find me on &lt;a href="https://www.linkedin.com/in/viram-choksi" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #webdev #javascript #react #portfolio #nextjs #frontend #career&lt;/p&gt;




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