<?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: Atul Srivastava</title>
    <description>The latest articles on DEV Community by Atul Srivastava (@imatulsrivas).</description>
    <link>https://dev.to/imatulsrivas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858888%2Fa4d42c97-360d-47b0-a8dd-7b2fdaccd718.jpg</url>
      <title>DEV Community: Atul Srivastava</title>
      <link>https://dev.to/imatulsrivas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imatulsrivas"/>
    <language>en</language>
    <item>
      <title>I built a VS Code extension so you can respond to GitHub Copilot agent from your phone</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Thu, 16 Apr 2026 16:26:08 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/i-built-a-vs-code-extension-so-you-can-respond-to-github-copilot-agent-from-your-phone-n0o</link>
      <guid>https://dev.to/imatulsrivas/i-built-a-vs-code-extension-so-you-can-respond-to-github-copilot-agent-from-your-phone-n0o</guid>
      <description>&lt;p&gt;Here's something that happens constantly with Copilot agent mode:&lt;/p&gt;

&lt;p&gt;You kick off a task. &lt;em&gt;"Refactor the auth module and update all the tests."&lt;/em&gt; Agent starts. You step away — meeting, lunch, whatever. Agent hits a confirmation: &lt;em&gt;"I need to delete 3 files. Proceed?"&lt;/em&gt; It sits there, waiting, doing nothing.&lt;/p&gt;

&lt;p&gt;You come back 40 minutes later. It stopped 38 minutes ago. You click "Yes." It continues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You just lost 38 minutes of autonomous work&lt;/strong&gt; — because the only way to interact with Copilot agent is through the VS Code window on your desktop.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Copilot Remote Control&lt;/strong&gt; to cut that chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Your phone becomes a live VS Code chat panel. Every message the agent prints appears on your phone in real time. When the agent stops and needs input, your phone &lt;strong&gt;buzzes in your pocket&lt;/strong&gt;. You glance at it, type "yes", put it back. Agent continues. You never left your meeting.&lt;/p&gt;

&lt;p&gt;No dead time. No wasted cycles.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture (no third-party server)
&lt;/h2&gt;

&lt;p&gt;This is not a wrapper around an API. There's no relay server I run. Here's what actually happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your PC (VS Code)                          Your Phone
┌─────────────────────┐                   ┌──────────────────┐
│  Copilot Agent      │                   │  PWA (React 18)  │
│       ↓             │                   │       ↑          │
│  Extension hooks    │                   │  WebSocket       │
│  into chat session  │                   │  client          │
│       ↓             │                   │       ↑          │
│  WebSocket Server   │◄── Cloudflare ───►│  Secure Tunnel   │
│  (localhost:3000)   │    Tunnel (wss)   │                  │
│  Push Notifier ─────┼──► Google FCM ───►│  Push Notif      │
└─────────────────────┘                   └──────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The WebSocket server runs &lt;em&gt;inside the extension&lt;/em&gt; on localhost. A Cloudflare quick tunnel (&lt;code&gt;cloudflared&lt;/code&gt; binary bundled in the extension) punches it out to the internet. Data flows directly from your PC to your phone through Cloudflare's network. I don't see your code. I don't store anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  The technical details worth knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Real-time streaming at 20ms:&lt;/strong&gt; The extension monitors Copilot's JSONL session files. VS Code writes response data in two modes — full snapshots (REPLACE) and incremental patches (SPLICE with an index offset). The extension reconstructs complete response state from both modes, diffs against what's been sent, and streams only new parts to your phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mid-stream reconnection:&lt;/strong&gt; If you open your phone after the agent has already been talking, you see the full accumulated response, not a blank screen. 200-message history buffer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offline license verification:&lt;/strong&gt; The license key is a JWT signed with RSA-2048. The extension verifies it using a baked-in public key — pure math, no network call. Works on air-gapped machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0/month infrastructure:&lt;/strong&gt; Vercel free tier + Firebase free tier + Cloudflare free tier. The entire backend is one serverless function that wakes up for 200ms per sale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup in 4 steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Buy &amp;amp; activate&lt;/strong&gt; at &lt;a href="https://agent-handle.vercel.app" rel="noopener noreferrer"&gt;agent-handle.vercel.app&lt;/a&gt; (₹49 / ~$6 one-time). Paste the license key in VS Code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan the QR code&lt;/strong&gt; that appears in the sidebar panel with your phone camera.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walk away.&lt;/strong&gt; Your phone shows everything the agent is doing, in real time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respond when it buzzes.&lt;/strong&gt; Firebase FCM pushes a notification when the agent needs input.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also send files from your phone — screenshots, PDFs, requirement docs — directly into your VS Code workspace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🛒 &lt;strong&gt;Marketplace:&lt;/strong&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=atulhritik.copilot-remote" rel="noopener noreferrer"&gt;marketplace.visualstudio.com/items?itemName=atulhritik.copilot-remote&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💳 &lt;strong&gt;Buy license (₹49 / ~$6):&lt;/strong&gt; &lt;a href="https://agent-handle.vercel.app" rel="noopener noreferrer"&gt;agent-handle.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;Phone PWA:&lt;/strong&gt; &lt;a href="https://pwa-six-pi.vercel.app" rel="noopener noreferrer"&gt;pwa-six-pi.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One-time purchase, no subscription, no recurring costs. Curious what you think — especially about the tunnel approach.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a VS Code extension that auto-selects Ask/Plan/Agent mode for every Copilot prompt</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Thu, 16 Apr 2026 16:24:36 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/i-built-a-vs-code-extension-that-auto-selects-askplanagent-mode-for-every-copilot-prompt-116n</link>
      <guid>https://dev.to/imatulsrivas/i-built-a-vs-code-extension-that-auto-selects-askplanagent-mode-for-every-copilot-prompt-116n</guid>
      <description>&lt;p&gt;If you use GitHub Copilot, you've probably noticed something: most developers leave it on Agent mode all the time. It's the default. It feels powerful. But it's also expensive — Agent mode burns through 3,000+ tokens even for a question like "what does useState do?"&lt;/p&gt;

&lt;p&gt;Ask mode handles that in ~500 tokens. But who switches manually every time?&lt;/p&gt;

&lt;p&gt;That's the problem I built &lt;strong&gt;Prompt Router&lt;/strong&gt; to solve. It watches your prompt, classifies it in milliseconds, and opens Copilot in the right mode automatically — no clicks, no thinking, no token waste.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it classifies prompts
&lt;/h2&gt;

&lt;p&gt;It uses a hybrid approach: a heuristic keyword classifier blended 40/60 with a Naive Bayes ML model. Both run entirely locally — no network calls, no telemetry, nothing leaves your machine.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Triggered by&lt;/th&gt;
&lt;th&gt;Approximate token cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ask&lt;/td&gt;
&lt;td&gt;Explanations, definitions, debugging concepts&lt;/td&gt;
&lt;td&gt;~500 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;Architecture, design, step-by-step approach&lt;/td&gt;
&lt;td&gt;~800 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent&lt;/td&gt;
&lt;td&gt;Write/fix/create code, run terminal commands&lt;/td&gt;
&lt;td&gt;~3,000+ tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ML model also learns from your corrections. Every time you dismiss or override its suggestion, it adjusts. Over time it personalises to how you actually write prompts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three ways to use it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;Ctrl+Shift+R&lt;/code&gt; quick input (recommended)&lt;/strong&gt;&lt;br&gt;
Press the shortcut, type your prompt, and VS Code opens Copilot in the right mode with your prompt pre-filled. Zero friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;@smart&lt;/code&gt; participant&lt;/strong&gt;&lt;br&gt;
In any Copilot chat, type &lt;code&gt;@smart&lt;/code&gt; followed by your question. It shows the detected mode label and a one-click button if you want to switch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;@router&lt;/code&gt; participant&lt;/strong&gt;&lt;br&gt;
Type &lt;code&gt;@router&lt;/code&gt; to get a full score breakdown — Ask / Plan / Agent percentages — so you can see exactly why it made the call it did.&lt;/p&gt;

&lt;p&gt;There's also a &lt;strong&gt;Token Savings Dashboard&lt;/strong&gt; in the Command Palette that shows your cumulative savings over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The license system
&lt;/h2&gt;

&lt;p&gt;It's a one-time purchase: &lt;strong&gt;$2.9 / ₹19&lt;/strong&gt;. You get a license key instantly by email. Paste it into VS Code once (&lt;code&gt;Prompt Router: Activate License&lt;/code&gt;), and it works forever — no subscription, no cloud check-in.&lt;/p&gt;

&lt;p&gt;The key is a JWT signed with RSA-256. The extension verifies it offline using a baked-in public key. Up to 3 device activations; you can deactivate any slot at any time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🛒 &lt;strong&gt;Marketplace:&lt;/strong&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=atulhritik.prompt-router-ai" rel="noopener noreferrer"&gt;marketplace.visualstudio.com/items?itemName=atulhritik.prompt-router-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💳 &lt;strong&gt;Buy license ($2.9 / ₹19):&lt;/strong&gt; &lt;a href="https://prompt-router-buy.vercel.app/" rel="noopener noreferrer"&gt;prompt-router-buy.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/extmind/prompt-router" rel="noopener noreferrer"&gt;github.com/extmind/prompt-router&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dropped this last week. Would love feedback — especially if you've got ideas on improving the classifier for edge cases.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built 2 Chrome Extensions for ADHD Users &amp; Trust — Shipped to the Chrome Web Store</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:09:42 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/i-built-2-chrome-extensions-for-adhd-users-trust-shipped-to-the-chrome-web-store-522b</link>
      <guid>https://dev.to/imatulsrivas/i-built-2-chrome-extensions-for-adhd-users-trust-shipped-to-the-chrome-web-store-522b</guid>
      <description>&lt;p&gt;I just shipped &lt;strong&gt;2 Chrome extensions&lt;/strong&gt; to the Chrome Web Store, both priced at $1.99 lifetime. Here's what they do and how I built them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extensions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🧠 ADHD Web Simplifier
&lt;/h3&gt;

&lt;p&gt;The web is a distraction machine. ADHD Web Simplifier fixes that automatically when you open any page.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Kills autoplaying videos, animations, and flashing banners&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bionic Reading&lt;/strong&gt; mode — bolds the first half of every word for faster scanning&lt;/li&gt;
&lt;li&gt;Built-in &lt;strong&gt;Pomodoro timer&lt;/strong&gt; with focus/break cycles&lt;/li&gt;
&lt;li&gt;One-click "Reading Mode" that strips sidebars, ads, and clutter&lt;/li&gt;
&lt;li&gt;Accessibility-first — works on any site, no config needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;a href="https://chromewebstore.google.com/detail/ifgknolbbedckpkgpjldgcmphebagigb" rel="noopener noreferrer"&gt;Get ADHD Web Simplifier&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🛡️ TrustLayer
&lt;/h3&gt;

&lt;p&gt;Before you trust a website, TrustLayer scores it for you.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Real-time credibility score (0–100) in the browser toolbar&lt;/li&gt;
&lt;li&gt;Checks domain age, HTTPS, known scam databases, and content signals&lt;/li&gt;
&lt;li&gt;Visual trust badge on every page&lt;/li&gt;
&lt;li&gt;Detailed breakdown: why the score is what it is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;a href="https://chromewebstore.google.com/detail/dmbmfdgapnbdmfkkpddgkmhemppbobko" rel="noopener noreferrer"&gt;Get TrustLayer&lt;/a&gt;&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React 18 · TypeScript · Chrome MV3 · Vite 5 · Tailwind CSS · Canvas API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both extensions use &lt;strong&gt;Manifest V3&lt;/strong&gt; (the current Chrome standard) with service workers instead of background pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key MV3 Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Content script&lt;/strong&gt; for DOM manipulation:&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;// manifest.json&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;all_urls&amp;gt;&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="s2"&gt;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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content.js&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="s2"&gt;run_at&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="s2"&gt;document_idle&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;&lt;strong&gt;Message passing&lt;/strong&gt; between popup and content script:&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;// popup.tsx&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enableFocusMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// content.ts&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enableFocusMode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;applyFocusMode&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;h2&gt;
  
  
  Lessons from the Chrome Web Store Review
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy policy is mandatory&lt;/strong&gt; — even for simple extensions. I hosted mine on GitHub Pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions justify&lt;/strong&gt; — the review team asks why you need each permission. &lt;code&gt;activeTab&lt;/code&gt; only is safer than &lt;code&gt;tabs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots matter&lt;/strong&gt; — Chrome Web Store listing quality directly affects click-through rate&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try Them
&lt;/h2&gt;

&lt;p&gt;Both are &lt;strong&gt;$1.99 one-time&lt;/strong&gt; — no subscription, no account. Works on Chrome and all Chromium browsers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;a href="https://chromewebstore.google.com/detail/ifgknolbbedckpkgpjldgcmphebagigb" rel="noopener noreferrer"&gt;ADHD Web Simplifier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🛡️ &lt;a href="https://chromewebstore.google.com/detail/dmbmfdgapnbdmfkkpddgkmhemppbobko" rel="noopener noreferrer"&gt;TrustLayer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/atul0016/chrome-extensions" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chromeextensions</category>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built 6 Free Windows Desktop Apps with Tauri + Rust + React — Here's What I Learned</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Sun, 12 Apr 2026 17:08:38 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/i-built-6-free-windows-desktop-apps-with-tauri-rust-react-heres-what-i-learned-14k3</link>
      <guid>https://dev.to/imatulsrivas/i-built-6-free-windows-desktop-apps-with-tauri-rust-react-heres-what-i-learned-14k3</guid>
      <description>&lt;p&gt;Over the past few months I shipped &lt;strong&gt;6 free productivity utilities&lt;/strong&gt; for Windows, all built with &lt;strong&gt;Tauri v2 + Rust + React 18 + TypeScript&lt;/strong&gt;. Here's what the suite looks like and what I learned building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Apps
&lt;/h2&gt;

&lt;p&gt;All 6 are live on the Microsoft Store under my publisher name &lt;strong&gt;Illusanato&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;App&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🐢 &lt;strong&gt;WhySlowPC&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;System Monitor &amp;amp; slow-down analyser&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9N1XH2BLQF8Z" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🌐 &lt;strong&gt;NetDiag&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Network diagnostic &amp;amp; speed tester&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9P2BJJ56H2BF" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🖨️ &lt;strong&gt;PrintFix&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Printer troubleshooter &amp;amp; fix tool&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9MZ2LK2ZWM6F" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📁 &lt;strong&gt;FileMemory&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Smart file search &amp;amp; recent-files manager&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9P48LVK01FQG" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔄 &lt;strong&gt;SmartReboot&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Safe scheduled restart with open-app detection&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9NG98RH3THZT" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⌨️ &lt;strong&gt;UniversalKeys&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Global keyboard shortcut manager&lt;/td&gt;
&lt;td&gt;&lt;a href="https://apps.microsoft.com/detail/9NDGBB23D822" rel="noopener noreferrer"&gt;Download&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Tauri Instead of Electron?
&lt;/h2&gt;

&lt;p&gt;I've used &lt;strong&gt;Electron&lt;/strong&gt; extensively (my SA ERP project is built on it), but for these utility apps I needed something lighter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bundle size&lt;/strong&gt;: Tauri apps ship at ~3–8 MB vs Electron's 100+ MB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: Tauri uses the system WebView (WebView2 on Windows), not a bundled Chromium&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust backend&lt;/strong&gt;: Native performance for system calls — reading CPU metrics, querying printers, scanning files&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend:  React 18 · TypeScript · Vite · Tailwind CSS
Backend:   Rust (Tauri v2 commands)
Packaging: MSIX via Tauri bundler → Microsoft Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Tauri Concepts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tauri commands&lt;/strong&gt; let you call Rust functions from the React frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_cpu_usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// system metrics via sysinfo crate&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="nf"&gt;.refresh_cpu&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="nf"&gt;.global_cpu_info&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.cpu_usage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tauri-apps/api/tauri&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;usage&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;invoke&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;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_cpu_usage&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;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Share more Rust utility code&lt;/strong&gt; across apps from the start — I ended up duplicating window-management helpers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Tauri's updater plugin&lt;/strong&gt; for auto-updates instead of relying on MS Store updates alone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add more telemetry&lt;/strong&gt; (crash reports) — diagnosing Windows-specific bugs without user logs is hard&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try Them
&lt;/h2&gt;

&lt;p&gt;All apps are &lt;strong&gt;free&lt;/strong&gt;, no account required. If you're on Windows, give one a try and let me know what you think in the comments!&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://apps.microsoft.com/search/publisher?name=Illusanato" rel="noopener noreferrer"&gt;See all apps on MS Store&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>react</category>
      <category>windows</category>
    </item>
    <item>
      <title>Building BloomNest: A Full-Stack Childcare Management Platform with Angular 18 &amp; Appwrite</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Mon, 06 Apr 2026 12:19:18 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/building-bloomnest-a-full-stack-childcare-management-platform-with-angular-18-appwrite-199</link>
      <guid>https://dev.to/imatulsrivas/building-bloomnest-a-full-stack-childcare-management-platform-with-angular-18-appwrite-199</guid>
      <description>&lt;p&gt;Childcare centers run on spreadsheets, WhatsApp messages, and paper forms. I wanted to see if I could replace all of that with a single web app. The result is &lt;strong&gt;BloomNest&lt;/strong&gt; — a full-stack management platform for nurseries and kindergartens.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://bloomnest-mu.vercel.app/" rel="noopener noreferrer"&gt;https://bloomnest-mu.vercel.app/&lt;/a&gt;&lt;br&gt;
📦 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/atul0016/BloomNest" rel="noopener noreferrer"&gt;https://github.com/atul0016/BloomNest&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;BloomNest handles everything a childcare facility needs day-to-day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Child enrollment&lt;/strong&gt; — profiles, groups, allergies, parent linkage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staff management&lt;/strong&gt; — roles, status tracking, CRUD operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart scheduling&lt;/strong&gt; — weekly grid, shift blueprints, conflict detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time tracking&lt;/strong&gt; — clock in/out, daily logs, monthly summaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absence management&lt;/strong&gt; — request/approve workflow with calendar view&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics dashboard&lt;/strong&gt; — charts for attendance trends and staff coverage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parent portal&lt;/strong&gt; — view-only access to their own child's data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push notifications&lt;/strong&gt; — real-time alerts via Firebase Cloud Messaging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dark mode&lt;/strong&gt; — because obviously&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five distinct role dashboards: Admin, Board, Manager, Educator, and Parent — each seeing only what they need.&lt;/p&gt;




&lt;h2&gt;
  
  
  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;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Angular 18 (standalone components)&lt;/td&gt;
&lt;td&gt;Strong typing, scalable architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;Bootstrap 5.3 + Bootstrap Icons&lt;/td&gt;
&lt;td&gt;Fast, consistent layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charts&lt;/td&gt;
&lt;td&gt;Chart.js 4.4&lt;/td&gt;
&lt;td&gt;Lightweight, flexible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database + Auth storage&lt;/td&gt;
&lt;td&gt;Appwrite Cloud&lt;/td&gt;
&lt;td&gt;Real-time subscriptions, file storage, easy SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Firebase Auth&lt;/td&gt;
&lt;td&gt;Reliable, free tier, easy social login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push notifications&lt;/td&gt;
&lt;td&gt;Firebase Cloud Messaging&lt;/td&gt;
&lt;td&gt;Battle-tested for web push&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Zero-config Angular deploys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Angular 18's standalone components (no NgModule) made the folder structure much cleaner. Lazy loading each feature module kept the initial bundle small.&lt;/p&gt;




&lt;h2&gt;
  
  
  The interesting architectural decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Splitting auth between Firebase and Appwrite
&lt;/h3&gt;

&lt;p&gt;Firebase handles identity (sign-in, password reset, token). Appwrite handles everything else — user profiles, role assignments, all app data. The two talk through a thin service layer that keeps the rest of the app unaware of which provider does what.&lt;/p&gt;

&lt;p&gt;This sounds like it adds complexity, but in practice it meant I could swap either provider independently. Firebase Auth's email verification flow is excellent; Appwrite's database permissions model is excellent. Both together = best of both worlds.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Role-based routing without a library
&lt;/h3&gt;

&lt;p&gt;Rather than pulling in a full RBAC library, I implemented guard-based routing using Angular's CanActivate. Each route declares which roles can access it. A RoleGuard checks the current user's role from a service and redirects if unauthorized.&lt;/p&gt;

&lt;p&gt;Simple, type-safe, zero extra dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Shift blueprints for scheduling efficiency
&lt;/h3&gt;

&lt;p&gt;Managers spend a lot of time building the same weekly schedule. I added "shift blueprints" — reusable shift templates that can be applied to any week with one click. It stores the pattern, not the instances, so changing a blueprint does not retroactively alter past schedules.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Real-time updates without polling
&lt;/h3&gt;

&lt;p&gt;Appwrite's real-time subscriptions push changes instantly to connected clients. When an admin approves an absence request, the educator's view updates without a page refresh. The subscription teardown on ngOnDestroy prevents memory leaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Appwrite permissions model&lt;/strong&gt; — Appwrite uses document-level permissions. Getting role-based read/write to work correctly (especially for the parent portal's read-only access to child documents) required careful collection design. I ended up using server-side functions for anything sensitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart.js with live data&lt;/strong&gt; — Chart.js does not automatically re-render when Angular's change detection runs. I had to call chart.update() manually after data changes, which meant tracking chart instances across components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Angular 18 signals&lt;/strong&gt; — I started using Angular signals for reactive state in a few components. The mental model is cleaner than RxJS for simple derived values (computed()), but integrating signals with Appwrite's observable SDK took some bridging work.&lt;/p&gt;




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

&lt;p&gt;You can log in and explore any role:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Email&lt;/th&gt;
&lt;th&gt;Password&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Admin&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:aria.thornewood@bloomnest.app"&gt;aria.thornewood@bloomnest.app&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;admin123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manager&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:caspian.drake@bloomnest.app"&gt;caspian.drake@bloomnest.app&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;manager123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Educator&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:elara.finch@bloomnest.app"&gt;elara.finch@bloomnest.app&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;educator123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parent&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:diana.whitmore@bloomnest.app"&gt;diana.whitmore@bloomnest.app&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;parent123&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Export reports to PDF/Excel&lt;/li&gt;
&lt;li&gt;Mobile app (Ionic + Capacitor, same Angular codebase)&lt;/li&gt;
&lt;li&gt;Multi-facility support (one account, multiple locations)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're building with Angular 18 or Appwrite and have questions about any of the patterns above, drop them in the comments. Happy to go deeper on any section.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building an Offline-First Multi-Tenant SaaS with React 19 and Dexie.js</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Sat, 04 Apr 2026 15:40:40 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/building-an-offline-first-multi-tenant-saas-with-react-19-and-dexiejs-4f1k</link>
      <guid>https://dev.to/imatulsrivas/building-an-offline-first-multi-tenant-saas-with-react-19-and-dexiejs-4f1k</guid>
      <description>&lt;p&gt;When I set out to build &lt;strong&gt;ParkManager&lt;/strong&gt; — a multi-tenant parking management SaaS — I knew "works offline" wasn't optional. Parking lots don't shut down when the internet goes out.&lt;/p&gt;

&lt;p&gt;Here's how I architected an offline-first, multi-tenant application using &lt;strong&gt;React 19&lt;/strong&gt;, &lt;strong&gt;Dexie.js&lt;/strong&gt; (IndexedDB wrapper), and &lt;strong&gt;Appwrite&lt;/strong&gt; as the cloud backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Most SaaS apps assume always-on connectivity. But in the real world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internet can be spotty at parking lots&lt;/li&gt;
&lt;li&gt;Attendants need to issue tickets regardless of connection&lt;/li&gt;
&lt;li&gt;Payment records can't be lost&lt;/li&gt;
&lt;li&gt;Multi-tenant data must stay isolated&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Stack Overview
&lt;/h3&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;Frontend&lt;/td&gt;
&lt;td&gt;React 19 + Vite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local DB&lt;/td&gt;
&lt;td&gt;Dexie.js (IndexedDB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Backend&lt;/td&gt;
&lt;td&gt;Appwrite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop&lt;/td&gt;
&lt;td&gt;Electron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PWA&lt;/td&gt;
&lt;td&gt;Service Workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Role-Based Access Control (5 roles)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Offline-First with Dexie.js
&lt;/h3&gt;

&lt;p&gt;The core idea: &lt;strong&gt;write locally first, sync to cloud when available&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Dexie database schema&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&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;Dexie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ParkManagerDB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&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;stores&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;++id, vehicleNumber, entryTime, exitTime, status, tenantId, synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;++id, ticketId, amount, method, tenantId, synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;++id, email, role, tenantId&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;Every record gets a &lt;code&gt;synced&lt;/code&gt; flag. When online, a background sync job pushes unsynced records to Appwrite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncToCloud&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;unsynced&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ticket&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;unsynced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;appwrite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;DB_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TICKETS_COLLECTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ticket&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;ticket&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticket&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;synced&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Will retry on next sync cycle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run sync every 30 seconds when online&lt;/span&gt;
&lt;span class="nf"&gt;setInterval&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onLine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;syncToCloud&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Tenancy
&lt;/h3&gt;

&lt;p&gt;Each record includes a &lt;code&gt;tenantId&lt;/code&gt;. The Dexie queries always filter by tenant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTickets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tickets&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tenantId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entryTime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the Appwrite side, collection-level permissions ensure tenants can only access their own data.&lt;/p&gt;

&lt;h3&gt;
  
  
  5 RBAC Roles
&lt;/h3&gt;

&lt;p&gt;ParkManager supports 5 distinct roles with different permissions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt; — Full system access, can manage tenants&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin&lt;/strong&gt; — Manage parking lots, users, and reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operator&lt;/strong&gt; — Day-to-day operations, view reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attendant&lt;/strong&gt; — Issue/close tickets, collect payments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountant&lt;/strong&gt; — View-only access to financial reports
&lt;/li&gt;
&lt;/ol&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;PERMISSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;owner&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;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;admin&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;manage_lots&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;manage_users&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;view_reports&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;manage_tickets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;operator&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;manage_tickets&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;view_reports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;attendant&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;create_ticket&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;close_ticket&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;collect_payment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;accountant&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;view_reports&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;view_payments&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hasPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&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;perms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PERMISSIONS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&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;perms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&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="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;perms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&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;
  
  
  PWA + Electron = Everywhere
&lt;/h3&gt;

&lt;p&gt;The same React codebase runs as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PWA&lt;/strong&gt; — installable on any device with a browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Electron app&lt;/strong&gt; — native desktop experience with system tray, auto-updates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web app&lt;/strong&gt; — standard browser access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Electron wrapper is thin:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BrowserWindow&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;electron&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createWindow&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;win&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;BrowserWindow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;webPreferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;nodeIntegration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contextIsolation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// In production, load the built React app&lt;/span&gt;
  &lt;span class="nx"&gt;win&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/index.html&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenReady&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createWindow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Offline-first isn't hard&lt;/strong&gt; — Dexie.js makes IndexedDB pleasant to work with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync conflicts are the real challenge&lt;/strong&gt; — use timestamps and "last write wins" for simple cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy at the data layer&lt;/strong&gt; — tenant isolation should be enforced at every query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC from day one&lt;/strong&gt; — retrofitting permissions is painful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React 19 + Vite&lt;/strong&gt; — blazing fast dev experience with HMR&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo&lt;/strong&gt;: &lt;a href="https://atul0016.github.io/park-manager/" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/atul0016/park-manager" rel="noopener noreferrer"&gt;github.com/atul0016/park-manager&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're building SaaS products that need to work in unreliable network conditions, offline-first architecture is worth the investment. The user experience improvement is dramatic.&lt;/p&gt;

&lt;p&gt;What offline-first patterns have you used in your projects? Drop a comment below!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built a Full-Scale ERP System as a Solo Developer</title>
      <dc:creator>Atul Srivastava</dc:creator>
      <pubDate>Fri, 03 Apr 2026 08:30:22 +0000</pubDate>
      <link>https://dev.to/imatulsrivas/how-i-built-a-full-scale-erp-system-as-a-solo-developer-4dpj</link>
      <guid>https://dev.to/imatulsrivas/how-i-built-a-full-scale-erp-system-as-a-solo-developer-4dpj</guid>
      <description>&lt;p&gt;Most developers avoid ERP. It's complex, boring, and usually built by teams of 20+. I built one alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Indian small businesses run on disconnected spreadsheets, Tally, and WhatsApp. I wanted to build one clean desktop app that handles everything — finance, inventory, sales, purchase, GST, manufacturing, HRM, and reporting.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Electron&lt;/strong&gt; — Desktop delivery (Windows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React + TypeScript&lt;/strong&gt; — UI layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; — Local-first data storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; — IPC handlers and service layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why desktop + SQLite instead of a web app? Indian SMEs often have unreliable internet. A local-first app that just works, every time, was the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Inside — 8 Modules
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Finance:&lt;/strong&gt; Chart of accounts, journal entries, general ledger, trial balance, P&amp;amp;L, balance sheet. Double-entry accounting built from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inventory:&lt;/strong&gt; Item master, warehouse structure, stock movement, valuations, low-stock alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales:&lt;/strong&gt; Customer management, sales orders, GST-compliant invoicing, receipt tracking, aging analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Purchase:&lt;/strong&gt; Vendor management, purchase orders, goods receipt, purchase invoicing, payment tracking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manufacturing:&lt;/strong&gt; Bill of materials, work centers, production orders, MRP planning, job work, quality control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HRM:&lt;/strong&gt; Employee records, attendance, leave management, payroll, tax declarations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GST &amp;amp; Compliance:&lt;/strong&gt; E-invoice, e-way bill, GSTR reports, ITC reconciliation, HSN summaries. Built for Indian tax workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reports:&lt;/strong&gt; Financial, sales, purchase, inventory, and GST reports with export options.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Domain modeling
&lt;/h3&gt;

&lt;p&gt;ERP isn't one problem, it's 15 interconnected problems. A stock movement affects inventory valuations, which affects financial reports, which affects GST filings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Double-entry accounting
&lt;/h3&gt;

&lt;p&gt;Getting the chart of accounts, journal entries, and ledger posting right took longer than any UI work.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. GST compliance
&lt;/h3&gt;

&lt;p&gt;Indian GST rules change constantly. Building a flexible structure that handles e-invoicing, reverse charges, and ITC was the most research-heavy part.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keeping it simple
&lt;/h3&gt;

&lt;p&gt;The biggest temptation in ERP is feature creep. I forced myself to keep the UI clean: clear navigation, card-based actions, search-first header.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build the data model first.&lt;/strong&gt; If your chart of accounts is wrong, everything downstream breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite is underrated.&lt;/strong&gt; For local-first business software, it's fast, reliable, and zero-config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ERP is a product exercise, not just a coding exercise.&lt;/strong&gt; You need to understand business operations, not just API design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship module by module.&lt;/strong&gt; I built Finance first, then Inventory, then Sales — each one tested before moving on.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://indian-erp.vercel.app" rel="noopener noreferrer"&gt;indian-erp.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href="https://github.com/atul0016/sa-erp" rel="noopener noreferrer"&gt;github.com/atul0016/sa-erp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://beimatulportfolio.tech" rel="noopener noreferrer"&gt;beimatulportfolio.tech&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're thinking about building business software as a solo developer — do it. The domain complexity is what makes it valuable, both as a product and on your portfolio.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Atul Srivastava, a full-stack developer building ERP systems, SaaS platforms, and Chrome extensions. Open to freelance and remote work. Reach me at &lt;a href="mailto:imatulsrivas@gmail.com"&gt;imatulsrivas@gmail.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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