<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ravgeet's Blog]]></title><description><![CDATA[Ravgeet's Blog]]></description><link>https://hashnode.ravgeet.in</link><generator>RSS for Node</generator><lastBuildDate>Mon, 08 Jun 2026 20:01:51 GMT</lastBuildDate><atom:link href="https://hashnode.ravgeet.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Real-Time Power Outage Monitor with ESP32 and Slack]]></title><description><![CDATA[The moment of realization happened in Singapore. I was thousands of miles away from home, enjoying a trip, when I went to check my home CCTV footage through my phone. The screen stayed black. "Connect]]></description><link>https://hashnode.ravgeet.in/building-a-real-time-power-outage-monitor-with-esp32-and-slack</link><guid isPermaLink="true">https://hashnode.ravgeet.in/building-a-real-time-power-outage-monitor-with-esp32-and-slack</guid><category><![CDATA[ESP32]]></category><category><![CDATA[arduino]]></category><category><![CDATA[hardware]]></category><category><![CDATA[iot]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Tue, 17 Mar 2026 05:48:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/613c8b1b22b7a41dfe5fc089/9d526864-c838-4d42-b165-0ea8f94b7f20.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The moment of realization happened in Singapore. I was thousands of miles away from home, enjoying a trip, when I went to check my home CCTV footage through my phone. The screen stayed black. "Connection Failed."</p>
<p>The internal monologue of a developer immediately goes to the worst-case scenario: <em>Did the router die? Is there a break-in? Did the server crash?</em></p>
<p>The reality was much simpler, yet equally frustrating: a power cut. The cameras ran on their internal batteries until they hit 0%, leaving me in a complete information blackout. I didn't know if the power was out for ten minutes or ten hours.</p>
<p>I promised myself I wouldn't leave for another trip without a "Heartbeat" from my home in Amritsar.</p>
<h3>The Solution</h3>
<p>I needed a non-invasive system (no cutting 220V mains wires), resilient to inverter switchover gaps, and capable of sending instant notifications.</p>
<p>I chose the <strong>ESP32</strong> for its built-in Wi-Fi and low power consumption. By pairing it with a Slack Webhook, I created a device that "shouts" the second the grid fails.</p>
<h3>The Hardware Stack</h3>
<p>To keep things modular and "plug-and-play," I went with:</p>
<ul>
<li><p><strong>ESP32 DevKit V1:</strong> The brain of the operation.</p>
</li>
<li><p><strong>USB-TTL Converter:</strong> This acts as the sensor. It brings the 5V USB signal down to a safe 3.3V for the ESP32 to read.</p>
</li>
<li><p><strong>1000µF Capacitor:</strong> Essential for bridging the 20ms gap during inverter switchover. This prevents the ESP32 from rebooting during the transition.</p>
</li>
<li><p>Female-to-Female jumper wires</p>
</li>
<li><p>Some USB data cables</p>
</li>
</ul>
<h3>Engineering the "Flicker Filter"</h3>
<p>One of the biggest hurdles was electrical noise. GPIO 34 on the ESP32 is an input-only pin and can act like an antenna. Without a solid ground reference, the signal "flickered" between ON and OFF.</p>
<p>I solved this with a two-pronged approach:</p>
<ol>
<li><p><strong>Grounding Geometry:</strong> Moving the ground wires to the same side of the board to stabilize the reference voltage.</p>
</li>
<li><p><strong>Software Debounce:</strong> I implemented a 3-second delay in the code. The system verifies that the power is <em>actually</em> out before sending a Slack alert, preventing false alarms from minor grid fluctuations.</p>
</li>
</ol>
<h3>Watch the Journey</h3>
<p>I documented the entire process—from navigating the local electronics markets in my city to the final "Production" test where I manually tripped the MCB.</p>
<ul>
<li><p><strong>Watch the Documentary:</strong> <a href="https://youtu.be/WmGZCAdeHMY">YouTube - Making my first Electronics project</a></p>
</li>
<li><p><strong>Get the Code:</strong> <a href="https://github.com/ravgeetdhillon/esp32-power-alert">GitHub - esp32-power-alert</a></p>
</li>
</ul>
<h3>Closing Thoughts</h3>
<p>Engineering isn't just about writing code for a Jira ticket; it's about solving the small, personal anxieties of life through technology. Now, when I’m on vacation, I’ll know exactly what’s happening at home. Not because I’m checking a camera, but because my home is talking to me.</p>
<p><em>If you're interested in building this yourself, feel free to reach out or check out the repository!</em></p>
]]></content:encoded></item><item><title><![CDATA[Debugging and Stopping Infinite Render Loops in React]]></title><description><![CDATA[Infinite renders are not magic bugs — they are deterministic feedback loops. Once you understand why a render retriggers itself, they become easy to reproduce, debug, and prevent.
This post walks through a step‑by‑step mental model to stop the “Maxim...]]></description><link>https://hashnode.ravgeet.in/debugging-and-stopping-infinite-render-loops-in-react</link><guid isPermaLink="true">https://hashnode.ravgeet.in/debugging-and-stopping-infinite-render-loops-in-react</guid><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Next.js]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Mon, 02 Feb 2026 10:30:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770024886941/2dbeb986-7b5e-4af3-8d1e-c73c563744bc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Infinite renders are not magic bugs — they are deterministic feedback loops. Once you understand <em>why</em> a render retriggers itself, they become easy to reproduce, debug, and prevent.</p>
<p>This post walks through a <strong>step‑by‑step mental model</strong> to stop the “Maximum update depth reached“ errors for good.</p>
<h2 id="heading-what-an-infinite-render-loop-really-is">What an Infinite Render Loop Really Is</h2>
<p>At its core, an infinite render loop looks like this:</p>
<ol>
<li><p>Component renders</p>
</li>
<li><p>Some reactive logic runs (effect, watcher, computed, subscription)</p>
</li>
<li><p>That logic updates the state</p>
</li>
<li><p>State update causes a re-render</p>
</li>
<li><p>Repeat forever</p>
</li>
</ol>
<p>The key insight:</p>
<blockquote>
<p><strong>Renders don’t loop by accident — they loop because state changes on every render.</strong></p>
</blockquote>
<h2 id="heading-the-fastest-way-to-reproduce-the-bug">The Fastest Way to Reproduce the Bug</h2>
<p>When debugging, your first goal is <strong>reproduction</strong>.</p>
<h3 id="heading-minimal-reproduction-checklist">Minimal reproduction checklist</h3>
<p>Comment out everything except one state value and one reactive hook (effect/watcher). Then add a log in both render and state update.</p>
<p>Example:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Component</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'render'</span>);

  <span class="hljs-keyword">const</span> [count, setCount] = useState(<span class="hljs-number">0</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'effect'</span>);
    setCount(count + <span class="hljs-number">1</span>);
  }, [count]);
}
</code></pre>
<p>If you see:</p>
<pre><code class="lang-plaintext">render → effect → render → effect → ...
</code></pre>
<p>You’ve confirmed the loop.</p>
<h2 id="heading-the-1-root-cause-unstable-references">The #1 Root Cause: Unstable References</h2>
<p>Most infinite loops come from <strong>reference instability</strong>, not logic errors.</p>
<h3 id="heading-identity-vs-equality">Identity vs Equality</h3>
<pre><code class="lang-js">{} !== {}
[] !== []
() =&gt; {} !== <span class="hljs-function">() =&gt;</span> {}
</code></pre>
<p>Even if values <em>look</em> equal, <strong>their identity changes</strong> on every render.</p>
<h3 id="heading-example-object-in-dependencies">Example: Object in dependencies</h3>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Component</span>(<span class="hljs-params">{ userId }</span>) </span>{
  <span class="hljs-keyword">const</span> filters = { <span class="hljs-attr">active</span>: <span class="hljs-literal">true</span>, userId };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchData(filters);
  }, [filters]); <span class="hljs-comment">// ❌ new object every render</span>
}
</code></pre>
<p>This results in the effect that runs forever.</p>
<h2 id="heading-stabilizing-references-with-memoization">Stabilizing References with Memoization</h2>
<h3 id="heading-fix-with-memoization">Fix with memoization</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> filters = useMemo(<span class="hljs-function">() =&gt;</span> ({
  <span class="hljs-attr">active</span>: <span class="hljs-literal">true</span>,
  userId
}), [userId]);
</code></pre>
<p>Now, it has the same reference, but the effect runs only when <code>userId</code> changes. A rule of thumb is that if a variable goes into the dependency array, it must be referentially stable.</p>
<h2 id="heading-choosing-correct-dependencies-not-fewer">Choosing Correct Dependencies (Not Fewer)</h2>
<p>A common anti-pattern:</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (status === <span class="hljs-string">'loaded'</span>) <span class="hljs-keyword">return</span>;
  setStatus(<span class="hljs-string">'loaded'</span>);
}, [status]);
</code></pre>
<p>This effect updates its own dependency once, then converges to a stable state instead of looping forever.</p>
<h2 id="heading-linting-that-actually-helps">Linting That Actually Helps</h2>
<p>Linting is one of the <strong>cheapest ways</strong> to prevent infinite render loops <em>before</em> they reach runtime — especially in React and Next.js apps.</p>
<p>By specifying the correct React Hooks Rules, you can catch the most common causes of render loops. BY using correct linting rules, you can enable rules in your editor. For example:</p>
<ul>
<li><p><code>react-hooks/rules-of-hooks</code></p>
</li>
<li><p><code>react-hooks/exhaustive-deps</code></p>
</li>
</ul>
<p>This forces correct dependency lists, exposes unstable references early, and prevents stale closures disguised as "fixes".</p>
<p>But still, the warnings about missing dependencies are not always true. It makes sense to treat them as <strong>design bugs</strong>, not suggestions.</p>
<h2 id="heading-debugging-with-why-did-you-render-llms">Debugging with <code>why-did-you-render</code> + LLMs</h2>
<p>Although we are talking about this step at the very last, if time is crucial to you, then this could be the first resort.</p>
<p>Sometimes you <em>know</em> a component is re-rendering too much, but you don’t know <strong>what changed</strong>. Multiple states or effects might be responsible for an infinite render loop.</p>
<p>This is where <code>why-did-you-render</code> becomes extremely powerful — especially when paired with an LLM.</p>
<h3 id="heading-what-why-did-you-render-does">What <code>why-did-you-render</code> does</h3>
<p><code>why-did-you-render</code> monkey-patches React in development and logs <strong>exact reasons</strong> for re-renders:</p>
<ul>
<li><p>Which props changed</p>
</li>
<li><p>Whether the change was by <strong>identity</strong> or <strong>value</strong></p>
</li>
<li><p>Which hooks triggered the update</p>
</li>
</ul>
<p>Instead of guessing, you get concrete evidence.</p>
<h3 id="heading-basic-setup">Basic setup</h3>
<pre><code class="lang-plaintext">[why-did-you-render]
MyComponent re-rendered because of props changes:
  props.filters changed
    prev: { active: true, userId: 1 }
    next: { active: true, userId: 1 }
  reason: props.filters !== prev.props.filters
</code></pre>
<p>This immediately tells you:</p>
<ul>
<li><p>The values are equal, but the <strong>reference is not</strong></p>
</li>
<li><p>Memoization is missing</p>
</li>
</ul>
<h3 id="heading-feeding-logs-to-an-llm-copilot-chatgpt">Feeding logs to an LLM (Copilot / ChatGPT)</h3>
<p>Here’s where debugging gets <em>much</em> faster.</p>
<p>You can save the console logs as a <code>.log</code> file and feed them to the LLM agent along with the code context that you feel might be the reason for the infinite rendering. You can use the following prompt:</p>
<blockquote>
<p>"I have attached the console logs from why-did-you-render along with the code in which the infinite loop is happening. Can you find what is causing this behaviour and how do I stabilize it?"</p>
</blockquote>
<p>Because the logs already encode <strong>identity vs equality</strong>, the LLM can:</p>
<ul>
<li><p>Identify unstable objects/functions</p>
</li>
<li><p>Suggest correct <code>useMemo</code> / <code>useCallback</code> placement</p>
</li>
<li><p>Detect unnecessary props drilling</p>
</li>
<li><p>Recommend architectural fixes (lifting state, memo boundaries)</p>
</li>
</ul>
<p>This removes the guesswork that usually slows humans down.</p>
<h3 id="heading-why-this-works-so-well-with-llms">Why this works so well with LLMs</h3>
<p>LLMs struggle with <em>implicit runtime behavior</em>.</p>
<p><code>why-did-you-render</code> turns runtime behavior into <strong>explicit text</strong>.</p>
<p>Once behavior is textual:</p>
<blockquote>
<p>Debugging becomes a reasoning problem — which LLMs are good at.</p>
</blockquote>
<p>Used together, they form a tight loop:</p>
<ol>
<li><p>Reproduce the render issue</p>
</li>
<li><p>Capture <code>why-did-you-render</code> logs</p>
</li>
<li><p>Paste logs + code into an LLM</p>
</li>
<li><p>Apply fix</p>
</li>
<li><p>Verify render stability</p>
</li>
</ol>
<h2 id="heading-final-thought">Final Thought</h2>
<p>Infinite render loops are not a framework flaw — they are a <strong>signal</strong>.</p>
<p>They tell you that:</p>
<ul>
<li><p>Data flow is unstable</p>
</li>
<li><p>Identity is misunderstood</p>
</li>
<li><p>Or side effects are misplaced</p>
</li>
</ul>
<p>Once you respect reference stability and dependency correctness, infinite loops disappear — permanently.</p>
]]></content:encoded></item><item><title><![CDATA[Rebuilding My Static Blog with Build-Time Data and Instant Search]]></title><description><![CDATA[Static sites are supposed to be fast, simple, and reliable. But over time, my personal blog started behaving like a dynamic app - runtime API calls, pagination logic everywhere, and fragmented view counts spread across platforms.
Last week, I rebuilt...]]></description><link>https://hashnode.ravgeet.in/rebuilding-my-static-blog-with-build-time-data-and-instant-search</link><guid isPermaLink="true">https://hashnode.ravgeet.in/rebuilding-my-static-blog-with-build-time-data-and-instant-search</guid><category><![CDATA[Nuxt]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[website]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Thu, 29 Jan 2026 18:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769509123113/95179792-58e0-47aa-b33d-96fc716c0b99.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Static sites are supposed to be fast, simple, and reliable. But over time, my personal blog started behaving like a dynamic app - runtime API calls, pagination logic everywhere, and fragmented view counts spread across platforms.</p>
<p>Last week, I rebuilt the blog section of <strong>ravgeet.in</strong> (Nuxt.js) to fix this properly. The end result is still a static site, but now it feels <em>alive</em>: aggregated view counts, instant search and sorting, and zero runtime dependencies on external APIs.</p>
<p>This post walks through the thinking, architecture, and trade-offs behind that rebuild.</p>
<h2 id="heading-the-problem-with-my-old-setup">The problem with my old setup</h2>
<p>Originally, my blog worked like this:</p>
<ul>
<li><p>Blog content lived on <strong>Hashnode</strong> (canonical source)</p>
</li>
<li><p>Some posts were also cross-posted to <strong>Dev.to</strong></p>
</li>
<li><p>Pages fetched blog data <strong>at runtime</strong> using Hashnode’s GraphQL API</p>
</li>
<li><p>Pagination logic (<code>hasNextPage</code>, cursors) lived inside the UI</p>
</li>
</ul>
<p>This had a few downsides:</p>
<ul>
<li><p>A static site depending on live APIs felt wrong</p>
</li>
<li><p>Local development and builds were slower and flaky</p>
</li>
<li><p>Adding features like search or sorting would require more APIs</p>
</li>
</ul>
<p>I wanted the blog to stay static - but smarter.</p>
<h2 id="heading-build-time-data-as-a-contract">Build-time data as a contract</h2>
<p>The core decision was simple:</p>
<blockquote>
<p><strong>Move all external data fetching to build time, and treat the result as immutable static data.</strong></p>
</blockquote>
<p>Instead of fetching blogs at runtime, I introduced a build step that:</p>
<ol>
<li><p>Fetches blogs from Hashnode</p>
</li>
<li><p>Fetches articles from Dev.to</p>
</li>
<li><p>Matches the same article across platforms</p>
</li>
<li><p>Aggregates view counts</p>
</li>
<li><p>Writes everything into a single JSON file</p>
</li>
</ol>
<p>At runtime, the site only reads from that JSON.</p>
<pre><code class="lang-plaintext">Hashnode + Dev.to
        ↓
Build-time fetch &amp; normalize
        ↓
static/blogs.json
        ↓
Nuxt UI (search, sort, views)
</code></pre>
<p>This one decision simplified everything else.</p>
<h2 id="heading-fetching-and-aggregating-blog-data">Fetching and aggregating blog data</h2>
<h3 id="heading-hashnode-canonical-content">Hashnode: canonical content</h3>
<p>Hashnode remains the source of truth for:</p>
<ul>
<li><p>Title, slug, content, tags</p>
</li>
<li><p>Publish date</p>
</li>
<li><p>Cover image</p>
</li>
<li><p>Base view count</p>
</li>
</ul>
<p>I fetch all posts using Hashnode’s GraphQL API with pagination handled inside a Node.js script.</p>
<h3 id="heading-devto-distribution-and-extra-reach">Dev.to: distribution and extra reach</h3>
<p>Dev.to is where additional readers come from, so ignoring those views felt wrong.</p>
<p>Using the Dev.to API (with a personal access token), I fetch all my articles and extract:</p>
<ul>
<li><p><code>slug</code></p>
</li>
<li><p><code>canonical_url</code></p>
</li>
<li><p><code>page_views_count</code></p>
</li>
</ul>
<h3 id="heading-matching-articles-across-platforms">Matching articles across platforms</h3>
<p>This is the tricky part. Articles are matched using a layered strategy:</p>
<ol>
<li><p><strong>Slug match</strong></p>
</li>
<li><p><strong>Canonical URL match</strong></p>
</li>
<li><p><strong>Title match</strong></p>
</li>
</ol>
<p>Once matched, the final view count becomes:</p>
<pre><code class="lang-plaintext">combinedViews = hashnodeViews + devtoViews
</code></pre>
<p>The output for each blog includes:</p>
<ul>
<li><p>Combined views</p>
</li>
<li><p>Platform-specific views (for debugging)</p>
</li>
<li><p>Dev.to URL (if matched)</p>
</li>
</ul>
<h2 id="heading-writing-the-static-data-contract">Writing the static data contract</h2>
<p>All processed data is written to the <code>static/blogs.json</code> file.</p>
<p>This file is:</p>
<ul>
<li><p>Generated at build time</p>
</li>
<li><p>Git-ignored</p>
</li>
<li><p>Treated as read-only by the app</p>
</li>
</ul>
<p>It also includes metadata like the last updated time and the total blog count.</p>
<p>This JSON file effectively replaces my entire blog API.</p>
<h2 id="heading-replacing-runtime-apis-with-static-services">Replacing runtime APIs with static services</h2>
<p>Previously, <code>services/blogs.js</code> made live GraphQL calls. After the refactor:</p>
<ul>
<li><p>The service dynamically imports <code>blogs.json</code></p>
</li>
<li><p><code>find</code>, <code>findOne</code>, and <code>search</code> all operate locally</p>
</li>
<li><p>No Axios</p>
</li>
<li><p>No pagination state</p>
</li>
<li><p>No network failures</p>
</li>
</ul>
<p>From the UI’s perspective, nothing changed - but under the hood, everything became predictable.</p>
<h2 id="heading-instant-search-and-sorting">Instant search and sorting</h2>
<p>Once all blog data is local, search becomes trivial.</p>
<p>I added:</p>
<ul>
<li><p>Client-side text search (title, brief, tags)</p>
</li>
<li><p>Sorting by:</p>
<ul>
<li><p>Published date (recent / oldest)</p>
</li>
<li><p>View count (most / least)</p>
</li>
</ul>
</li>
</ul>
<p>Because the dataset is small and static:</p>
<ul>
<li><p>Search results are instant</p>
</li>
<li><p>No debouncing hacks</p>
</li>
<li><p>No loading states</p>
</li>
<li><p>Sorting is deterministic</p>
</li>
</ul>
<p>This dramatically improves discoverability without introducing a search service.</p>
<h2 id="heading-trade-offs-and-lessons-learned">Trade-offs and lessons learned</h2>
<p>This approach isn’t perfect:</p>
<ul>
<li><p>Build time increases slightly</p>
</li>
<li><p>The JSON file grows over time</p>
</li>
<li><p>It’s not suitable for real-time analytics</p>
</li>
</ul>
<p>But for a personal blog, the trade-offs are worth it.</p>
<p>The key takeaways from the refactor that made me realize that:</p>
<ul>
<li><p>Static doesn’t mean lifeless</p>
</li>
<li><p>Build-time data pipelines are underrated</p>
</li>
<li><p>One clean data contract simplifies UI, UX, and performance</p>
</li>
</ul>
<p>If you’re curious, the full implementation lives in the <a target="_blank" href="https://github.com/ravgeetdhillon/ravgeet-web">ravgeet.in repository</a>.</p>
]]></content:encoded></item><item><title><![CDATA[My 7 Aspirations as a Software Engineer in 2026]]></title><description><![CDATA[2026 feels like a genuine inflection point in my software engineering journey. By February 2026, I will have completed four years as a professional engineer, starting with my first full-time role at CloudAnswers in February 2022. At this stage, my as...]]></description><link>https://hashnode.ravgeet.in/my-7-aspirations-as-a-software-engineer-in-2026</link><guid isPermaLink="true">https://hashnode.ravgeet.in/my-7-aspirations-as-a-software-engineer-in-2026</guid><category><![CDATA[#growth]]></category><category><![CDATA[software development]]></category><category><![CDATA[personal development]]></category><category><![CDATA[engineering]]></category><category><![CDATA[leadership]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Sun, 25 Jan 2026 06:30:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769247203368/313620ca-4f36-44d0-b5d0-429ab1f01b86.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>2026 feels like a genuine inflection point in my software engineering journey. By February 2026, I will have completed four years as a professional engineer, starting with my first full-time role at CloudAnswers in February 2022. At this stage, my aspirations are less about titles or rapid jumps and more about clarity — how I think, the kinds of problems I choose to solve, and the impact I want my work to have over time.</p>
<p>This post is not a checklist of goals. Instead, it’s a reflection on the <em>direction</em> I want my career to move in — as an engineer who values strong fundamentals, meaningful leverage, and sustainable, long-term growth.</p>
<h2 id="heading-1-mastering-the-fundamentals">1. Mastering the Fundamentals</h2>
<p>By 2026, my primary aspiration is to be <strong>fundamentally strong</strong>.</p>
<p>Not just "good at React" or "familiar with backend systems" but genuinely comfortable explaining <em>why</em> things behave the way they do:</p>
<ul>
<li><p>How JavaScript works under the hood</p>
</li>
<li><p>How browsers render, schedule, and optimize work</p>
</li>
<li><p>How React actually reconciles, schedules, and re-renders</p>
</li>
<li><p>How data flows through a system end-to-end</p>
</li>
</ul>
<p>I want to be the engineer who can debug issues calmly because I understand the system — not because I’ve memorized fixes.</p>
<h2 id="heading-2-thinking-in-systems-not-just-features">2. Thinking in Systems, Not Just Features</h2>
<p>Another aspiration is to shift from <strong>feature-level thinking</strong> to <strong>system-level thinking</strong>.</p>
<p>Instead of asking:</p>
<blockquote>
<p>“How do I implement this requirement?”</p>
</blockquote>
<p>I want my default question to be:</p>
<blockquote>
<p>“How does this decision affect the system six months from now?”</p>
</blockquote>
<p>That means caring about:</p>
<ul>
<li><p>Trade-offs</p>
</li>
<li><p>Maintainability</p>
</li>
<li><p>Operational complexity</p>
</li>
<li><p>Developer experience</p>
</li>
</ul>
<p>Good engineers ship features. Great engineers design systems that <em>survive change</em>. This shift usually happens when you’re trusted to own a product end‑to‑end, responsible not just for fixing bugs or adding features, but for the long‑term health and evolution of the entire system.</p>
<h2 id="heading-3-becoming-a-strong-communicator-not-just-a-coder">3. Becoming a Strong Communicator, Not Just a Coder</h2>
<p>By 2026, I want my value to extend beyond code.</p>
<p>This includes:</p>
<ul>
<li><p>Explaining complex ideas clearly to other engineers</p>
</li>
<li><p>Writing thoughtful PR descriptions and design docs</p>
</li>
<li><p>Adding working evidence in the PRs in the form of videos and screenshots</p>
</li>
<li><p>Helping juniors build correct mental models</p>
</li>
<li><p>Disagreeing respectfully and productively</p>
</li>
</ul>
<p>Software engineering is a team sport. Clear thinking is useless if it can’t be communicated.</p>
<h2 id="heading-4-building-leverage-through-tools-and-automation">4. Building Leverage Through Tools and Automation</h2>
<p>One of my strongest aspirations is to <strong>build leverage</strong>.</p>
<p>Instead of solving the same problems repeatedly, I want to:</p>
<ul>
<li><p>Automate workflows</p>
</li>
<li><p>Build internal tools</p>
</li>
<li><p>Create systems that scale my impact beyond my own output</p>
</li>
</ul>
<p>Leverage is what separates engineers who work <em>hard</em> from engineers who work <em>effectively</em>.</p>
<h2 id="heading-5-developing-taste-and-judgment">5. Developing Taste and Judgment</h2>
<p>Technical skills can be learned. <strong>Judgment takes time.</strong></p>
<p>By 2026, I want to develop a strong engineering taste:</p>
<ul>
<li><p>Knowing when <em>not</em> to over-engineer</p>
</li>
<li><p>Knowing when technical debt is acceptable</p>
</li>
<li><p>Choosing boring solutions when they’re the right ones</p>
</li>
</ul>
<p>This kind of judgment only comes from reflection, mistakes, and intentional learning.</p>
<h2 id="heading-6-staying-curious-without-burning-out">6. Staying Curious Without Burning Out</h2>
<p>Finally, I aspire to stay curious — but sustainable.</p>
<p>Not chasing every new framework, but:</p>
<ul>
<li><p>Learning deeply</p>
</li>
<li><p>Picking tools intentionally</p>
</li>
<li><p>Balancing ambition with health</p>
</li>
</ul>
<p>Longevity matters. I want a career that compounds, not one that exhausts me early.</p>
<h2 id="heading-7-launching-at-least-one-production-grade-app">7. Launching at Least One Production-grade App</h2>
<p>By the end of 2026, I want to have launched <strong>at least one app that real users can use</strong> — not just a side project living on GitHub.</p>
<p>This aspiration is less about building a startup and more about <strong>closing the loop</strong> as an engineer. From idea to execution to feedback, I want to experience the full lifecycle:</p>
<ul>
<li><p>Identifying a real problem (even a small one)</p>
</li>
<li><p>Designing a simple, opinionated solution</p>
</li>
<li><p>Making trade-offs under real constraints</p>
</li>
<li><p>Shipping, maintaining, and iterating based on usage</p>
</li>
</ul>
<p>Building a personal app forces a different level of ownership. There is no product manager, no deadline imposed by someone else, and no ambiguity about responsibility. If something is broken, unclear, or poorly designed — it’s on me.</p>
<p>More importantly, it sharpens judgment. You quickly learn what <em>actually</em> matters to users, which abstractions are worth the cost, and which technical decisions age poorly once real usage begins.</p>
<h2 id="heading-closing-thoughts">Closing Thoughts</h2>
<p>My aspirations for 2026 are less about <em>where</em> I work and more about <em>how</em> I work and think.</p>
<p>If I can be a calmer problem-solver, a clearer thinker, and an engineer who builds systems that last — I’ll consider myself on the right path.</p>
<p>I plan to revisit this post at the end of the year to assess how closely my reality aligned with these intentions, honestly.</p>
]]></content:encoded></item><item><title><![CDATA[Realtime Deploy Notifications in Next.js with Toasts]]></title><description><![CDATA[Ever deployed a new version of your app and wished your users got notified instantly while they’re still using the old one? In this post, I'll show you how I built a live build checker in my Next.js app that detects when a new deployment is live and ...]]></description><link>https://hashnode.ravgeet.in/realtime-deploy-notifications-in-nextjs-with-toasts</link><guid isPermaLink="true">https://hashnode.ravgeet.in/realtime-deploy-notifications-in-nextjs-with-toasts</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[SaaS]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[product development]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Sat, 24 Jan 2026 09:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769245089949/ae0d75b0-a29b-414b-adb5-5e0bbc9ad823.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ever deployed a new version of your app and wished your users got notified <strong>instantly</strong> while they’re still using the old one? In this post, I'll show you how I built a <strong>live build checker</strong> in my Next.js app that detects when a new deployment is live and gently nudges users to refresh using a toast notification.</p>
<blockquote>
<p>Use case: Helpful for apps deployed on Vercel where static pages and edge functions make it easy to serve outdated content across sessions.</p>
</blockquote>
<h2 id="heading-the-stack">The Stack</h2>
<ul>
<li><p><strong>Next.js 15</strong></p>
</li>
<li><p><strong>React 19</strong></p>
</li>
<li><p><strong>React-Bootstrap</strong></p>
</li>
<li><p><strong>React-Toastify</strong></p>
</li>
<li><p><strong>Vercel Edge Functions</strong></p>
</li>
<li><p><strong>Supabase Auth (optional)</strong></p>
</li>
</ul>
<h2 id="heading-the-concept">The Concept</h2>
<p>When a new build is deployed, a unique timestamp (<code>NEXT_PUBLIC_BUILD_TIMESTAMP</code>) is injected into the environment. On the client, we <strong>poll an API endpoint</strong> every few minutes to check if the server's build timestamp has changed. If it has, we notify the user.</p>
<h2 id="heading-step-by-step-implementation">Step-by-Step Implementation</h2>
<h3 id="heading-1-add-a-build-timestamp-during-build-time">1. Add a build timestamp during build time</h3>
<p>In <code>next.config.ts</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString();

<span class="hljs-keyword">const</span> nextConfig = {
  env: {
    NEXT_PUBLIC_BUILD_TIMESTAMP: now,
  },
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> nextConfig;
</code></pre>
<p>This ensures every build has a unique timestamp baked in at compile time.</p>
<h3 id="heading-2-create-a-apibuild-route">2. Create a <code>/api/build</code> route</h3>
<p>This edge function returns the current server build timestamp:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { NextRequest } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> runtime = <span class="hljs-string">"edge"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">request: NextRequest</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(
    <span class="hljs-built_in">JSON</span>.stringify({ build: process.env.NEXT_PUBLIC_BUILD_TIMESTAMP }),
    {
      headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
    }
  );
}
</code></pre>
<p>You can also add authentication here if needed, e.g., for private dashboards.</p>
<h3 id="heading-3-build-a-custom-hook-uselivebuildchecker">3. Build a custom hook: <code>useLiveBuildChecker</code></h3>
<p>This hook polls the <code>/api/build</code> endpoint periodically and triggers a toast if it detects a new version:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { useEffect, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-toastify"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useLiveBuildChecker</span>(<span class="hljs-params">intervalMin = 5</span>) </span>{
  <span class="hljs-keyword">const</span> currentBuild = useRef(
    process.env.NEXT_PUBLIC_BUILD_TIMESTAMP
  );

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> checkForUpdate = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">"/api/build"</span>);
        <span class="hljs-keyword">const</span> { build } = res.data;

        <span class="hljs-keyword">if</span> (build &amp;&amp; build !== currentBuild.current) {
          toast.info(<span class="hljs-string">"A new version of this page is available. Refresh to see the latest changes."</span>, {
            autoClose: <span class="hljs-literal">false</span>,
            position: <span class="hljs-string">"bottom-right"</span>,
          });
          <span class="hljs-built_in">clearInterval</span>(interval); <span class="hljs-comment">// stop further polling</span>
        }
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Update check failed:"</span>, e);
      }
    };

    <span class="hljs-keyword">const</span> interval = <span class="hljs-built_in">setInterval</span>(
      checkForUpdate,
      process.env.NODE_ENV === <span class="hljs-string">"development"</span> ? <span class="hljs-number">5</span> * <span class="hljs-number">1000</span> : intervalMin * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>
    );

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">clearInterval</span>(interval);
  }, [intervalMin]);
}
</code></pre>
<h3 id="heading-4-add-toast-ui-hook-in-layout">4. Add Toast UI + Hook in Layout</h3>
<p>Update your app layout to include the <code>ToastContainer</code> and run the hook:</p>
<pre><code class="lang-tsx">"use client";

import { ReactNode } from "react";
import { ToastContainer } from "react-toastify";
import { useLiveBuildChecker } from "@/hooks/useLiveBuildChecker";

import "react-toastify/dist/ReactToastify.css";

interface Props {
  children: ReactNode;
}

export default function AppLayout({ children }: Props) {
  useLiveBuildChecker(); // default 5-min interval

  return (
    &lt;&gt;
      {children}
      &lt;ToastContainer /&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h2 id="heading-result">Result</h2>
<p>Whenever a new version of your app is deployed, users still active in the browser will get this neat toast:</p>
<blockquote>
<p><strong>"</strong>A new version of this page is available. Refresh to see the latest changes.<strong>"</strong></p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769244335784/ba5fed26-3f32-4300-8a6c-53b504f6bd0f.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-why-this-works">Why This Works</h2>
<p>This approach is fully <strong>edge-friendly</strong>, <strong>stateless</strong>, and <strong>easy to scale</strong>. And unlike service worker-based update detection, it works great for <strong>SSR/ISR setups</strong> and <strong>custom dashboards</strong>.</p>
<h2 id="heading-bonus-ideas">Bonus Ideas</h2>
<ul>
<li><p>You can trigger a background update via service workers.</p>
</li>
<li><p>You can track how long users stay on an outdated version.</p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Keeping users on the latest version of your app improves stability, security, and experience. With just a few lines of code, you can achieve this kind of real-time deploy awareness in any Next.js app.</p>
<p>If you're building a dashboard, personal tool, or even a SaaS product, this is one of those <em>delightfully simple yet powerful</em> improvements.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built a Unified Calendar Dashboard with Next.js, Vercel Edge Functions & No Database]]></title><description><![CDATA[Problem
I was juggling tasks across:

Company ClickUp (for team collaboration)

Notion (for personal to-dos and planning)

Google Calendar (from both company & personal emails)


The chaos was real. I was missing due dates, spending too much time jum...]]></description><link>https://hashnode.ravgeet.in/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-and-no-database</link><guid isPermaLink="true">https://hashnode.ravgeet.in/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-and-no-database</guid><category><![CDATA[General Programming]]></category><category><![CDATA[app development]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Tue, 04 Nov 2025 12:58:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762260694520/c760b5d3-bd6e-42d1-b954-495840544610.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-problem">Problem</h2>
<p>I was juggling tasks across:</p>
<ul>
<li><p>Company ClickUp (for team collaboration)</p>
</li>
<li><p>Notion (for personal to-dos and planning)</p>
</li>
<li><p>Google Calendar (from both company &amp; personal emails)</p>
</li>
</ul>
<p>The chaos was real. I was missing due dates, spending too much time jumping between apps, and lacked a single place to glance at all my tasks.</p>
<h2 id="heading-the-solution">The Solution</h2>
<p>I built a <strong>read-only personal dashboard</strong> that:</p>
<ul>
<li><p>Aggregates tasks/events from ClickUp, Notion, and Google Calendar</p>
</li>
<li><p>Groups tasks as <strong>Overdue</strong>, <strong>Due Today</strong>, <strong>Upcoming by Date,</strong> and <strong>No Due Date</strong></p>
</li>
<li><p>Runs entirely on <strong>Next.js + Edge Functions</strong></p>
</li>
<li><p>Uses <strong>no database</strong>, just live API reads</p>
</li>
<li><p>Stores my daily tasks that I need to do</p>
</li>
<li><p>Is <strong>password protected</strong> and deployed on a Vercel subdomain</p>
</li>
</ul>
<p>Here’s how it looks after multiple polishes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762317912196/cd3dabc8-6e00-4f7c-9aac-12d07bd79b02.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<ul>
<li><p><strong>Frontend</strong>: Next.js App Router + React Bootstrap + TanStack Query</p>
</li>
<li><p><strong>API Layer</strong>: Vercel Edge Functions</p>
</li>
<li><p><strong>Auth</strong>: Cookie-based with middleware protection</p>
</li>
<li><p><strong>Hosting</strong>: Vercel subdomain</p>
</li>
</ul>
<h2 id="heading-core-features">Core Features</h2>
<h3 id="heading-unified-task-view">Unified Task View</h3>
<p>Each task is grouped into:</p>
<ul>
<li><p>Overdue</p>
</li>
<li><p>Due Today / Tomorrow</p>
</li>
<li><p>Upcoming</p>
</li>
<li><p>No Due Date</p>
</li>
</ul>
<p>It pulls data from these 3 APIs:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> [clickup, notion, calendar] = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([
  fetchClickupTasks(),
  fetchNotionTasks(),
  fetchCalendarEvents(),
]);
</code></pre>
<h3 id="heading-auth-with-middleware">Auth with Middleware</h3>
<p>I implemented simple cookie-based authentication to protect my dashboard. The middleware runs on every request and checks for a valid auth cookie before allowing access to protected routes.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// middleware.ts</span>
<span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">middleware</span>(<span class="hljs-params">request: NextRequest</span>) </span>{
  <span class="hljs-keyword">const</span> auth = request.cookies.get(<span class="hljs-string">"auth"</span>);
  <span class="hljs-keyword">const</span> pathname = request.nextUrl.pathname;

  <span class="hljs-keyword">const</span> publicPaths = [<span class="hljs-string">"/login"</span>, <span class="hljs-string">"/api/login"</span>, <span class="hljs-string">"/api/logout"</span>];
  <span class="hljs-keyword">if</span> (publicPaths.includes(pathname)) <span class="hljs-keyword">return</span> NextResponse.next();
  <span class="hljs-keyword">if</span> (auth?.value === <span class="hljs-string">"1"</span>) <span class="hljs-keyword">return</span> NextResponse.next();

  <span class="hljs-keyword">if</span> (pathname.startsWith(<span class="hljs-string">"/api/"</span>)) {
    <span class="hljs-keyword">return</span> NextResponse.json({ error: <span class="hljs-string">"Unauthorized"</span> }, { status: <span class="hljs-number">401</span> });
  }

  <span class="hljs-keyword">return</span> NextResponse.redirect(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"/login"</span>, request.url));
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
  matcher: [
    <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"/dashboard"</span>,
    <span class="hljs-string">"/api/events"</span>,
    <span class="hljs-string">"/api/clickup"</span>,
    <span class="hljs-string">"/api/notion"</span>,
    <span class="hljs-string">"/api/calendar"</span>,
  ],
};
</code></pre>
<h3 id="heading-modular-api-fetchers">Modular API Fetchers</h3>
<p>I kept the codebase clean by separating each API integration into its own module. This makes it easier to maintain and test each data source independently.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// lib/sources/clickup.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchClickupTasks</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://api.clickup.com/api/v2/..."</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> res.json();
}
</code></pre>
<p>The same goes for Notion &amp; Google Calendar.</p>
<h3 id="heading-server-api-route-example">Server API Route Example</h3>
<p>The backend API routes handle data aggregation from all sources. This route fetches tasks from all three platforms simultaneously and returns them as a unified JSON response.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// app/api/events/route.ts</span>
<span class="hljs-keyword">import</span> { getAllEvents } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/getAllEvents"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { clickup, notion, calendar } = <span class="hljs-keyword">await</span> getAllEvents();
  <span class="hljs-keyword">return</span> Response.json({ clickup, notion, calendar });
}
</code></pre>
<h2 id="heading-frontend-ui">Frontend UI</h2>
<p>The frontend uses TanStack Query for efficient data fetching with automatic caching and background updates. This ensures the dashboard stays responsive while keeping data fresh.</p>
<p>Using TanStack Query for live fetching and caching:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> { data, isLoading } = useQuery({
  queryKey: [<span class="hljs-string">"events"</span>],
  queryFn: <span class="hljs-function">() =&gt;</span> fetch(<span class="hljs-string">"/api/events"</span>).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.json()),
});
</code></pre>
<p>Then we categorize tasks by due date:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> overdue = allTasks.filter(<span class="hljs-function"><span class="hljs-params">task</span> =&gt;</span> isBefore(task.dueDate, today));
<span class="hljs-keyword">const</span> dueToday = allTasks.filter(<span class="hljs-function"><span class="hljs-params">task</span> =&gt;</span> isToday(task.dueDate));
<span class="hljs-keyword">const</span> upcoming = groupByDate(allTasks.filter(...));
<span class="hljs-keyword">const</span> noDueDate = allTasks.filter(<span class="hljs-function"><span class="hljs-params">task</span> =&gt;</span> !task.dueDate);
</code></pre>
<p>The dashboard also includes a "Today's Work List" feature where I can curate specific tasks from across all platforms. This has become my morning ritual - selecting what I want to focus on for the day creates clarity and intention around my daily goals.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762259973414/af1f68b6-cb04-4746-b7f7-d9025de8ed09.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762259962064/435daf88-ef06-490e-bdd4-15633323ec2c.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-deployment">Deployment</h2>
<p>I deployed the app to Vercel and created a subdomain via Hostinger by:</p>
<ol>
<li><p>Creating a subdomain DNS record</p>
</li>
<li><p>Adding the domain to Vercel</p>
</li>
<li><p>Setting env variables and password via the Vercel dashboard</p>
</li>
</ol>
<p>No secrets or tasks are stored — it's 100% live.</p>
<h2 id="heading-whats-next">What’s Next?</h2>
<p>I'm happy with the current version, but I could add the following features in the future:</p>
<ul>
<li><p>Month View toggle</p>
</li>
<li><p>Desktop notifications for overdue tasks</p>
</li>
<li><p>Auto-refresh every 10 mins</p>
</li>
<li><p>Tauri or Expo wrapper for mobile</p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>This project helped me regain clarity over my weekly tasks. I have pinned this dashboard in my browser and open it every morning to immediately see what matters. It's fast, reliable, and mine.</p>
<p>If you're tired of hopping between 5 apps, build something simple that fits your brain.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Smart Hardware Inventory System That Actually Works]]></title><description><![CDATA[We've all been there. You're rushing to meet a deadline and desperately need that specific USB drive—the one with the 32GB capacity that has your client's backup files. But as you stare at the drawer full of identical-looking pendrives, cables, and c...]]></description><link>https://hashnode.ravgeet.in/building-a-smart-hardware-inventory-system-that-actually-works</link><guid isPermaLink="true">https://hashnode.ravgeet.in/building-a-smart-hardware-inventory-system-that-actually-works</guid><category><![CDATA[Productivity]]></category><category><![CDATA[openai]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Sun, 12 Oct 2025 18:30:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759218185506/774ed865-391e-45b2-a959-e2a106cc230c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We've all been there. You're rushing to meet a deadline and desperately need that specific USB drive—the one with the 32GB capacity that has your client's backup files. But as you stare at the drawer full of identical-looking pendrives, cables, and chargers, you realize you have no idea which one is which.</p>
<p>Last month, I finally got tired of this digital scavenger hunt and decided to build something better. Not an enterprise-grade asset management system (who has time for that?), but a lightweight, practical solution that would actually solve my real-world problem.</p>
<p>Here's how I built a hardware inventory system that's simple enough to maintain and smart enough to find anything instantly.</p>
<h2 id="heading-the-problem-hardware-chaos">The Problem: Hardware Chaos</h2>
<p>My desk drawer looked like a tech graveyard. Multiple USB drives, various charging cables, adapters, and dongles—all visually identical but functionally different. The 8GB drive with personal photos looked exactly like the 64GB one with work projects. The USB-C cable that supports fast charging was indistinguishable from the data-only one.</p>
<p>Every time I needed something specific, I'd end up:</p>
<ul>
<li><p>Plugging in random drives to check their contents</p>
</li>
<li><p>Testing cables to see what they actually do</p>
</li>
<li><p>Wasting 10-15 minutes on something that should take 30 seconds</p>
</li>
</ul>
<p>There had to be a better way.</p>
<h2 id="heading-the-solution-smart-simplicity">The Solution: Smart Simplicity</h2>
<p>Instead of over-engineering this, I decided to build something that would work with tools I already use daily. My requirements were simple:</p>
<ol>
<li><p><strong>Quick to update</strong> - Adding new items shouldn't be a chore</p>
</li>
<li><p><strong>Accessible anywhere</strong> - No app installations or complex logins</p>
</li>
<li><p><strong>Physical integration</strong> - Must work with actual hardware, not just digital records</p>
</li>
<li><p><strong>Intelligent search</strong> - Find items by description, not just exact matches</p>
</li>
</ol>
<h2 id="heading-building-the-system-a-step-by-step-breakdown">Building the System: A Step-by-Step Breakdown</h2>
<h3 id="heading-step-1-the-foundation-google-sheets-as-a-database">Step 1: The Foundation - Google Sheets as a Database</h3>
<p>I started with a clean Google Sheet with these columns:</p>
<ul>
<li><p><strong>ID</strong>: Auto-generated unique identifier</p>
</li>
<li><p><strong>Type</strong>: Category like "USB Drive", "Cable", "Charger"</p>
</li>
<li><p><strong>Description</strong>: The magic field where I describe each item in plain English</p>
</li>
<li><p><strong>Date Added</strong>: Automatic timestamp</p>
</li>
<li><p><strong>Status</strong>: Available, In Use, Lost</p>
</li>
</ul>
<p>Nothing revolutionary here, but the key was keeping it simple enough that I'd actually maintain it.</p>
<h3 id="heading-step-2-smart-id-generation-with-apps-script">Step 2: Smart ID Generation with Apps Script</h3>
<p>Manual ID assignment? No thanks. I wrote a Google Apps Script function that generates short, unique IDs automatically:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateUniqueID</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> sheet = SpreadsheetApp.getActiveSheet();
  <span class="hljs-keyword">const</span> existingIDs = sheet.getRange(<span class="hljs-string">'A:A'</span>).getValues().flat();

  <span class="hljs-keyword">let</span> newID;
  <span class="hljs-keyword">do</span> {
    newID = <span class="hljs-built_in">Math</span>.random().toString(<span class="hljs-number">36</span>).substring(<span class="hljs-number">2</span>, <span class="hljs-number">5</span>).toUpperCase();
  } <span class="hljs-keyword">while</span> (existingIDs.includes(newID));

  <span class="hljs-keyword">return</span> newID;
}
</code></pre>
<p>This creates memorable 3-character IDs like <code>XR7</code> or <code>K2M</code>. Short enough to write on tiny labels, unique enough to avoid conflicts.</p>
<h3 id="heading-step-3-bridging-digital-and-physical-worlds">Step 3: Bridging Digital and Physical Worlds</h3>
<p>Here's where it gets practical. I ordered a pack of small adhesive labels and a fine-tip permanent marker. Every time I add a new item to the sheet:</p>
<ol>
<li><p>The system generates a unique ID</p>
</li>
<li><p>I write that ID on a physical label</p>
</li>
<li><p>I stick the label directly on the hardware</p>
</li>
</ol>
<p>Now every pendrive, cable, and charger has its own "name tag." When I need something, I just check the physical tag and look it up instantly.</p>
<p><strong>Pro tip</strong>: For items too small for labels (like tiny dongles), I use small zip-lock bags with labeled sticky notes.</p>
<h3 id="heading-step-4-web-access-without-the-hassle">Step 4: Web Access Without the Hassle</h3>
<p>Opening Google Sheets every time felt clunky. Instead, I:</p>
<ol>
<li><p>Published the sheet as a web app through Apps Script</p>
</li>
<li><p>Set up a custom subdomain using Vercel</p>
</li>
<li><p>Created a clean, mobile-friendly interface</p>
</li>
</ol>
<p>Now I can check my inventory from my phone while standing in front of my hardware drawer. Game-changer.</p>
<h3 id="heading-step-5-ai-powered-search-that-actually-understands">Step 5: AI-Powered Search That Actually Understands</h3>
<p>This is where the system gets genuinely smart. Instead of remembering exact product names or scrolling through rows, I integrated OpenAI search that understands natural language queries:</p>
<ul>
<li><p><em>"Find the Kingston drive with project files"</em></p>
</li>
<li><p><em>"Which cable charges my laptop?"</em></p>
</li>
<li><p><em>"Show me USB drives over 16GB"</em></p>
</li>
</ul>
<p>The AI reads through all my descriptions and instantly returns the matching items with their IDs. It's like having a personal assistant for my hardware drawer.</p>
<h2 id="heading-real-world-impact-why-this-actually-works">Real-World Impact: Why This Actually Works</h2>
<p><strong>Before</strong>: "I need that specific USB drive... <em>proceeds to test 6 different drives</em>"</p>
<p><strong>After</strong>: "I need that specific USB drive" → Check AI search → "It's the one labeled K2M" → Done in 30 seconds.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759218497213/0e38e669-e964-454d-aedf-fbedf2bdb230.png" alt class="image--center mx-auto" /></p>
<p>The system works because it addresses the actual problem, not the theoretical one. I don't need enterprise features like check-out workflows or depreciation tracking. I just need to find my stuff quickly.</p>
<h3 id="heading-the-numbers">The Numbers</h3>
<p>After 3 months of use:</p>
<ul>
<li><p><strong>45 items</strong> tracked (drives, cables, adapters, dongles)</p>
</li>
<li><p><strong>Average search time</strong>: 15 seconds (down from 10+ minutes)</p>
</li>
<li><p><strong>Time to add new item</strong>: 90 seconds</p>
</li>
</ul>
<h2 id="heading-lessons-learned">Lessons Learned</h2>
<h3 id="heading-what-works-really-well">What Works Really Well</h3>
<ul>
<li><p><strong>Physical labels are non-negotiable</strong> - Digital-only systems fail in the real world</p>
</li>
<li><p><strong>AI search is a multiplier</strong> - Turns a simple spreadsheet into something genuinely intelligent</p>
</li>
<li><p><strong>Simplicity scales</strong> - Started with pendrives, now tracks everything</p>
</li>
</ul>
<h2 id="heading-whats-next-future-improvements">What's Next: Future Improvements</h2>
<p>The foundation is solid, so I'm adding features that solve real pain points:</p>
<p><strong>QR Code Integration</strong>: Generate QR codes for each item that link directly to their details. Scan with phone → instant access.</p>
<p><strong>Capacity Analytics</strong>: Total up storage capacity across all drives, and identify gaps in my hardware collection.</p>
<h2 id="heading-the-bigger-picture">The Bigger Picture</h2>
<p>This tiny project reminded me why I love building solutions to real problems. Not every system needs machine learning, microservices, or a dedicated mobile app. Sometimes, Google Sheets, a bit of scripting, and some creativity are exactly the right tools.</p>
<p>The best productivity systems are the ones you actually use. And for keeping track of my ever-growing collection of tech accessories, this simple approach has been perfect.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Basic AI Agent]]></title><description><![CDATA[As developers, we constantly face a dilemma: how do we make our code flexible enough to handle natural human requests without hardcoding every possible scenario?
While working on various automation projects, I kept running into the same pattern—users...]]></description><link>https://hashnode.ravgeet.in/building-an-ai-powered-function-orchestrator</link><guid isPermaLink="true">https://hashnode.ravgeet.in/building-an-ai-powered-function-orchestrator</guid><category><![CDATA[AI]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[openai]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Thu, 02 Oct 2025 12:30:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759051142066/7eae6181-9d78-479c-b91e-f39a41468d4a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As developers, we constantly face a dilemma: <strong>how do we make our code flexible enough to handle natural human requests without hardcoding every possible scenario?</strong></p>
<p>While working on various automation projects, I kept running into the same pattern—users would ask for something in plain English, like <em>"Can you calculate 2+25-4^2?</em> or <em>"Process these files and send me a summary",</em> but my code was rigid, expecting specific formats and predefined workflows.</p>
<p>The breakthrough came when I realized: <strong>What if AI handled the planning, and I just focused on building solid, reusable functions?</strong> Instead of trying to anticipate every user request, let AI interpret the intent and dynamically orchestrate my functions.</p>
<p>This post walks through building a mini AI agent framework that separates <strong>what you can do</strong> (functions) from <strong>how to do it</strong> (AI planning). The result? Code that adapts to human intent rather than forcing humans to adapt to your interface.</p>
<h2 id="heading-the-problem-traditional-code-vs-human-intent">The Problem: Traditional Code vs. Human Intent</h2>
<p>How many times have you written code that looks like this?</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">complex_math_solver</span>(<span class="hljs-params">expression</span>):</span>
    <span class="hljs-comment"># Parse expression</span>
    <span class="hljs-comment"># Apply PEMDAS rules</span>
    <span class="hljs-comment"># Handle edge cases</span>
    <span class="hljs-comment"># Return result</span>
    <span class="hljs-keyword">pass</span>
</code></pre>
<p>The problem? You're cramming <strong>parsing logic</strong>, <strong>mathematical rules</strong>, and <strong>execution</strong> into one monolithic function. What if we could separate these concerns entirely?</p>
<h2 id="heading-the-solution-ai-as-a-function-orchestrator">The Solution: AI as a Function Orchestrator</h2>
<p>Instead of hardcoding business logic, what if we let AI handle the <strong>planning</strong> while we focus on building <strong>atomic, reusable functions</strong>?</p>
<p>Here's the architecture:</p>
<h3 id="heading-step-1-define-atomic-functions">Step 1: Define Atomic Functions</h3>
<p>First, we create simple, single-purpose functions that do one thing well. Think of these as your building blocks—each function is pure, testable, and completely independent. The key is keeping them atomic so AI can combine them in any order to solve complex problems.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sum</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a + b

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">multiply</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a * b

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">power</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a ** b

<span class="hljs-comment"># Function registry for dynamic execution</span>
FUNCTIONS = {
    <span class="hljs-string">"sum"</span>: sum,
    <span class="hljs-string">"multiply"</span>: multiply,
    <span class="hljs-string">"power"</span>: power,
}
</code></pre>
<h3 id="heading-step-2-document-your-functions-for-ai">Step 2: Document Your Functions (For AI)</h3>
<p>Next, we create clear documentation for each function. This isn't just good practice — it's essential for AI to understand what each function does and how to use it. Think of this as your function's "instruction manual" that AI reads to make smart planning decisions.</p>
<pre><code class="lang-python">DOCS = {
    <span class="hljs-string">"sum"</span>: {
        <span class="hljs-string">"description"</span>: <span class="hljs-string">"Add two numbers"</span>,
        <span class="hljs-string">"args"</span>: {
            <span class="hljs-string">"a"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
            <span class="hljs-string">"b"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
        },
    },
    <span class="hljs-string">"multiply"</span>: {
        <span class="hljs-string">"description"</span>: <span class="hljs-string">"Multiply two numbers"</span>,
        <span class="hljs-string">"args"</span>: {
            <span class="hljs-string">"a"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
            <span class="hljs-string">"b"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
        },
    },
    <span class="hljs-string">"power"</span>: {
        <span class="hljs-string">"description"</span>: <span class="hljs-string">"Raise a to the power of b"</span>,
        <span class="hljs-string">"args"</span>: {
            <span class="hljs-string">"a"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
            <span class="hljs-string">"b"</span>: {
                <span class="hljs-string">"type"</span>: <span class="hljs-string">"number"</span>,
                <span class="hljs-string">"description"</span>: <span class="hljs-string">"a float or int number"</span>,
            },
        },
    },
}
</code></pre>
<h3 id="heading-step-3-let-ai-generate-execution-plans">Step 3: Let AI Generate Execution Plans</h3>
<p>Finally, we let AI do the heavy lifting — interpreting natural language requests and creating step-by-step execution plans. The AI uses your function documentation to understand what's possible, then figures out the optimal sequence to achieve the user's goal.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_action_plan</span>(<span class="hljs-params">user_query</span>):</span>
    prompt = <span class="hljs-string">f"""
    You are an AI planner. Convert the user's request into a step-by-step plan.

    Available functions:
    <span class="hljs-subst">{json.dumps(DOCS, indent=<span class="hljs-number">2</span>)}</span>

    User's query: "<span class="hljs-subst">{user_query}</span>"

    Return a JSON plan with ordered steps.
    """</span>

    response = openai.chat.completions.create(
        model=<span class="hljs-string">"gpt-4o-mini"</span>,
        messages=[{<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}],
        temperature=<span class="hljs-number">0</span>
    )
    <span class="hljs-keyword">return</span> json.loads(response.choices[<span class="hljs-number">0</span>].message.content)
</code></pre>
<h2 id="heading-real-example-solve-22542">Real Example: "Solve 2+2*5+4^2"</h2>
<p>When a user sends this request to the system, it gets passed to OpenAI along with the function documentation. The AI analyzes the mathematical expression, applies PEMDAS rules, and returns a structured JSON plan that breaks down the calculation into atomic steps using the available functions.</p>
<p><strong>Input:</strong> Natural language request<br /><strong>AI Output:</strong> Structured execution plan</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"steps"</span>: [
    { <span class="hljs-attr">"function"</span>: <span class="hljs-string">"power"</span>, <span class="hljs-attr">"args"</span>: { <span class="hljs-attr">"a"</span>: <span class="hljs-number">4</span>, <span class="hljs-attr">"b"</span>: <span class="hljs-number">2</span> } },
    { <span class="hljs-attr">"function"</span>: <span class="hljs-string">"multiply"</span>, <span class="hljs-attr">"args"</span>: { <span class="hljs-attr">"a"</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">"b"</span>: <span class="hljs-number">5</span> } },
    { <span class="hljs-attr">"function"</span>: <span class="hljs-string">"sum"</span>, <span class="hljs-attr">"args"</span>: { <span class="hljs-attr">"a"</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">"b"</span>: <span class="hljs-string">"&lt;result_of_step_2&gt;"</span> } },
    {
      <span class="hljs-attr">"function"</span>: <span class="hljs-string">"sum"</span>,
      <span class="hljs-attr">"args"</span>: { <span class="hljs-attr">"a"</span>: <span class="hljs-string">"&lt;result_of_step_3&gt;"</span>, <span class="hljs-attr">"b"</span>: <span class="hljs-string">"&lt;result_of_step_1&gt;"</span> }
    }
  ]
}
</code></pre>
<p><strong>Execution Output:</strong></p>
<pre><code class="lang-plaintext">Step 1: power(4, 2) → 16
Step 2: multiply(2, 5) → 10
Step 3: sum(2, 10) → 12
Step 4: sum(12, 16) → 28

Final Result: 28
</code></pre>
<h2 id="heading-the-magic-dynamic-plan-execution">The Magic: Dynamic Plan Execution</h2>
<p>This function takes the AI-generated plan and executes it step by step. The key insight is <strong>result chaining</strong>—each step can reference outputs from previous steps using placeholders like <code>&lt;result_of_step_1&gt;</code>. The system automatically resolves these references, creating a dynamic pipeline where complex calculations emerge from simple function compositions.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">execute_plan</span>(<span class="hljs-params">plan</span>):</span>
    results = {}
    <span class="hljs-keyword">for</span> idx, step <span class="hljs-keyword">in</span> enumerate(plan[<span class="hljs-string">"steps"</span>], start=<span class="hljs-number">1</span>):
        func_name = step[<span class="hljs-string">"function"</span>]
        args = step[<span class="hljs-string">"args"</span>]

        <span class="hljs-comment"># Replace placeholders with previous results</span>
        <span class="hljs-keyword">for</span> k, v <span class="hljs-keyword">in</span> args.items():
            <span class="hljs-keyword">if</span> isinstance(v, str) <span class="hljs-keyword">and</span> v.startswith(<span class="hljs-string">"&lt;result_of_step_"</span>):
                step_idx = int(v.split(<span class="hljs-string">"_"</span>)[<span class="hljs-number">-1</span>].replace(<span class="hljs-string">"&gt;"</span>, <span class="hljs-string">""</span>))
                args[k] = results[step_idx]

        <span class="hljs-comment"># Execute function dynamically</span>
        func = FUNCTIONS[func_name]
        result = func(**args)
        results[idx] = result

        print(<span class="hljs-string">f"Step <span class="hljs-subst">{idx}</span>: <span class="hljs-subst">{func_name}</span>(<span class="hljs-subst">{args}</span>) → <span class="hljs-subst">{result}</span>"</span>)

    <span class="hljs-keyword">return</span> results[len(results)]
</code></pre>
<h2 id="heading-why-this-architecture-wins">Why This Architecture Wins</h2>
<h3 id="heading-separation-of-concerns"><strong>Separation of Concerns</strong></h3>
<ul>
<li><p><strong>You write:</strong> Pure, testable functions</p>
</li>
<li><p><strong>AI handles:</strong> Complex planning and orchestration</p>
</li>
<li><p><strong>System manages:</strong> Execution flow and state</p>
</li>
</ul>
<h3 id="heading-human-in-the-loop-safety"><strong>Human-in-the-Loop Safety</strong></h3>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> <span class="hljs-string">"error"</span> <span class="hljs-keyword">in</span> plan:
    print(<span class="hljs-string">"❌ Error in plan generation:"</span>)
    print(plan[<span class="hljs-string">"error"</span>][<span class="hljs-string">"message"</span>])
    sys.exit(<span class="hljs-number">1</span>)  <span class="hljs-comment"># Safe exit on planning failures</span>
</code></pre>
<p>For example, if a user asks <em>"Divide 10 by 0 and add 5"</em>, the AI can detect this is mathematically impossible and return an error response like:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: {
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Cannot divide by zero - this operation is undefined in mathematics"</span>
  }
}
</code></pre>
<p>This prevents dangerous operations from executing and provides clear feedback to users.</p>
<h3 id="heading-infinite-extensibility"><strong>Infinite Extensibility</strong></h3>
<p>Today, it's math functions:</p>
<pre><code class="lang-python">FUNCTIONS = {
    <span class="hljs-string">"sum"</span>: sum,
    <span class="hljs-string">"multiply"</span>: multiply,
    <span class="hljs-string">"divide"</span>: divide
}
</code></pre>
<p>Tomorrow it could be <strong>anything</strong>:</p>
<pre><code class="lang-python">FUNCTIONS = {
    <span class="hljs-string">"read_file"</span>: read_file,
    <span class="hljs-string">"send_email"</span>: send_email,
    <span class="hljs-string">"query_database"</span>: query_db,
    <span class="hljs-string">"fetch_weather"</span>: weather_api,
    <span class="hljs-string">"analyze_sentiment"</span>: sentiment,
}
</code></pre>
<h2 id="heading-real-world-applications">Real-World Applications</h2>
<h3 id="heading-file-operations">File Operations</h3>
<p><em>"Take all .txt files in /docs, extract headings, and create a summary document"</em></p>
<p><strong>Required Functions:</strong> <code>list_files()</code>, <code>read_file()</code>, <code>extract_headings()</code>, <code>create_document()</code>, <code>write_file()</code></p>
<h3 id="heading-api-orchestration">API Orchestration</h3>
<p><em>"Get weather for New York, if it's raining, send a Slack message to #general"</em></p>
<p><strong>Required Functions:</strong> <code>fetch_weather()</code>, <code>check_condition()</code>, <code>send_slack_message()</code></p>
<h3 id="heading-data-pipeline">Data Pipeline</h3>
<p><em>"Load sales.csv, calculate monthly averages, generate a chart, and email it to the team"</em></p>
<p><strong>Required Functions:</strong> <code>load_csv()</code>, <code>calculate_average()</code>, <code>group_by_month()</code>, <code>generate_chart()</code>, <code>send_email()</code></p>
<h3 id="heading-mcp-server-integration">MCP Server Integration</h3>
<p><em>"Query our customer database, analyze sentiment of recent feedback, and create a dashboard"</em></p>
<p><strong>Required Functions:</strong> <code>query_database()</code>, <code>analyze_sentiment()</code>, <code>aggregate_data()</code>, <code>create_dashboard()</code>, <code>save_report()</code></p>
<h2 id="heading-the-bigger-picture">The Bigger Picture</h2>
<p>This isn't just a math solver — it's a <strong>mini AI agent framework</strong>. The system provides:</p>
<ol>
<li><p><strong>Function Library:</strong> Atomic, reusable components</p>
</li>
<li><p><strong>AI Planner:</strong> Intelligent request interpretation</p>
</li>
<li><p><strong>Execution Engine:</strong> Safe, traceable function orchestration</p>
</li>
<li><p><strong>Human Oversight:</strong> Approval and error handling</p>
</li>
</ol>
<h2 id="heading-whats-next">What's Next?</h2>
<ol>
<li><p><strong>Add more function types</strong> (file ops, API calls)</p>
</li>
<li><p><strong>Implement approval workflows</strong> (show plan before execution)</p>
</li>
<li><p><strong>Add function validation</strong> (type checking, parameter validation)</p>
</li>
<li><p><strong>Build a web interface</strong> (make it accessible to non-developers)</p>
</li>
<li><p><strong>Integrate with MCP servers</strong> (extend to complex business logic)</p>
</li>
</ol>
<p><strong>The bottom line:</strong> Stop hardcoding business logic. Let AI handle the planning, you handle the implementation. The result? More flexible, maintainable, and extensible code that adapts to user intent rather than forcing users to adapt to your interface.</p>
]]></content:encoded></item><item><title><![CDATA[The ultimate guide to Python logging]]></title><description><![CDATA[When an application runs, it performs a tremendous number of tasks, with many happening behind the scenes. Even a simple to-do application has more than you'd expect. The app will at a bare minimum have tons of tasks like user logins, creating to-dos...]]></description><link>https://hashnode.ravgeet.in/the-ultimate-guide-to-python-logging</link><guid isPermaLink="true">https://hashnode.ravgeet.in/the-ultimate-guide-to-python-logging</guid><category><![CDATA[Python]]></category><category><![CDATA[Logs]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Tue, 19 Aug 2025 18:30:29 GMT</pubDate><content:encoded><![CDATA[<p>When an application runs, it performs a tremendous number of tasks, with many happening behind the scenes. Even a simple to-do application has more than you'd expect. The app will at a bare minimum have tons of tasks like user logins, creating to-dos, updating to-dos, deleting to-dos, and duplicating to-dos. The tasks an application performs can result in success or potentially result in some errors.</p>
<p>For anything you're running that has users, you'll need to at least consider monitoring events happening so that they can be analyzed to identify bottlenecks in the performance of the application. This is where <strong>logging</strong> is useful. Without logging, it's impossible to have insight or observability into what your application is actually doing.</p>
<p>In this article, you'll learn how to create logs in a Python application using the Python logging module. Logging can help Python developers of all experience levels develop and analyze an application's performance more quickly. Let's dig in!</p>
<p>Read the full blog on <a target="_blank" href="https://www.honeybadger.io/blog/python-logging/">Honeybadger</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.honeybadger.io/blog/python-logging/">https://www.honeybadger.io/blog/python-logging/</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p><a target="_blank" href="https://www.tiny.cloud/">I publish a monthly newsletter in which</a> I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac<a target="_blank" href="https://www.tiny.cloud/">ross while surfing the web.</a></p>
<p><a target="_blank" href="https://www.tiny.cloud/">Connect with</a> <a target="_blank" href="https://www.tiny.cloud/">me through Twitter • LinkedIn • Github or</a> send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[Working with Markdown in Python]]></title><description><![CDATA[If you use the Internet, you have surely come across the term Markdown. Markdown is a lightweight markup language that makes it very easy to write formatted content. It was created by John Gruber and Aaron Swartz in 2004. It uses very easy-to-remembe...]]></description><link>https://hashnode.ravgeet.in/working-with-markdown-in-python</link><guid isPermaLink="true">https://hashnode.ravgeet.in/working-with-markdown-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[markdown]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Thu, 07 Aug 2025 06:30:29 GMT</pubDate><content:encoded><![CDATA[<p>If you use the Internet, you have surely come across the term <strong>Markdown.</strong> <a target="_blank" href="https://daringfireball.net/projects/markdown/">Markdown</a> is a lightweight markup language that makes it very easy to write formatted content. It was created by John Gruber and Aaron Swartz in 2004. It uses very easy-to-remember syntax and is therefore used by many bloggers and content writers around the world. Even this blog that you are reading is written and formatted using Markdown.</p>
<p>Markdown is one of the most widely used formats for storing formatted <a target="_blank" href="https://daringfireball.net/projects/markdown/">data. It</a> easily integrates with Web technologies, as it can be converted to HTML or vice versa using Markdown compilers. It allows you to write HTML entities, such as headings, lists, images, links, tables, and more without much effort or code. It is used in blogs, content management systems, Wikis, documentation, and many more places.</p>
<p>In this article, you'll learn how to work with Markdown in a Python ap<a target="_blank" href="https://daringfireball.net/projects/markdown/">plicatio</a>n using different Python packages, including markdown, front matter, and markdownify.</p>
<p>Read the full blog on <a target="_blank" href="https://www.honeybadger.io/blog/python-markdown/">Honeybadger</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.honeybadger.io/blog/python-markdown/">https://www.honeybadger.io/blog/python-markdown/</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p><a target="_blank" href="https://www.tiny.cloud/">I publish a monthly newsletter in which</a> I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac<a target="_blank" href="https://www.tiny.cloud/">ross while surfing the web.</a></p>
<p><a target="_blank" href="https://www.tiny.cloud/">Connect with</a> <a target="_blank" href="https://www.tiny.cloud/">me through Twitter • LinkedIn • Github or</a> send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[Handling undo functions in rich text editors]]></title><description><![CDATA[Undo and redo operations are a must-have feature in any rich text editor – they’re a user's safety net. For a great user experience (UX), users need to solve their editing problems in a rich text editor.
An undo/redo button makes your users more conf...]]></description><link>https://hashnode.ravgeet.in/handling-undo-functions-in-rich-text-editors</link><guid isPermaLink="true">https://hashnode.ravgeet.in/handling-undo-functions-in-rich-text-editors</guid><category><![CDATA[Web Development]]></category><category><![CDATA[React]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Tue, 29 Jul 2025 18:30:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750429199494/62a8139a-dc56-4364-ac06-4587c9e57daf.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Undo and redo operations are a must-have feature in any rich text editor – they’re a user's safety net. For a great user experience (UX), users need to solve their editing problems in a rich text editor.</p>
<p>An undo/redo button makes your users more confident, because it’s a clear signal that if they make a mistake, they can easily undo and restore changes. For example, if a user accidentally deletes a paragraph, the undo function can restore their work – and spare them a lot of frustration.</p>
<p>However, implementing the undo/redo functionality is complicated. It requires an understanding of <a target="_blank" href="https://www.geeksforgeeks.org/stack-data-structure/"><strong>data structures like Stack</strong></a>.</p>
<p>You need to know which actions need to be pushed onto the stack, as well as when to push them, and the same goes for the pop operation.</p>
<p>In this article, you'll find out about the complexity of creating and maintaining the undo/redo functionality, and see how the <a target="_blank" href="https://www.tiny.cloud/tinymce/"><strong>TinyMCE rich text editor</strong></a> makes it easy.</p>
<p>Read the full blog on <a target="_blank" href="https://www.tiny.cloud/blog/undo-function-handling/">Tiny</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.tiny.cloud/blog/undo-function-handling/">https://www.tiny.cloud/blog/undo-function-handling/</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p><a target="_blank" href="https://www.tiny.cloud/">I publish a monthly newsletter in which</a> I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac<a target="_blank" href="https://www.tiny.cloud/">ross while surfing the web.</a></p>
<p><a target="_blank" href="https://www.tiny.cloud/">Connect with</a> <a target="_blank" href="https://www.tiny.cloud/">me through Twitter • LinkedIn • Github or</a> send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[How to enable in-app Notifications using TinyMCE APIs]]></title><description><![CDATA[Notifications add value to an app by helping to build conversations between users. They can also help make an interface less hostile by sharing important information. And it’s because they’re so useful that you’re being flooded by feature requests th...]]></description><link>https://hashnode.ravgeet.in/how-to-enable-in-app-notifications-using-tinymce-apis</link><guid isPermaLink="true">https://hashnode.ravgeet.in/how-to-enable-in-app-notifications-using-tinymce-apis</guid><category><![CDATA[General Programming]]></category><category><![CDATA[React]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Text Editors]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Wed, 16 Jul 2025 18:30:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750429108540/ee479c2d-5006-489f-bc2a-03cb8395da7a.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Notifications add value to an app by helping to build conversations between users. They can also help make an interface less hostile by sharing important information. And it’s because they’re so useful that you’re being flooded by feature requests that are asking for notifications to be added to your app. Now (please).</p>
<p>However, because of the noise, it’s hard to set aside the time to figure out the necessary requirements: does it need an opt-in and opt-out feature? Audience segmentation? Personalization? </p>
<p>The work involved just keeps piling up. </p>
<p>The good news is that in-app notifications (the kind of notifications that give useful information on screen after a change or event), are easy to handle with a <a target="_blank" href="https://www.tiny.cloud/"><strong>reliable rich text editor such as T</strong></a><a target="_blank" href="https://www.tiny.cloud/"><strong>inyMCE</strong>. The editor has a notifications AP</a>I that can communicate vital information to the user, and is easy and quick to set up. In this article, you'll find a guide on how to set up and manage notifications in the TinyMCE rich text editor, with NotificationManager API.</p>
<p>Read the full blog on <a target="_blank" href="https://www.tiny.cloud/blog/enable-in-app-notifications/">Tiny</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.tiny.cloud/blog/enable-in-app-notifications/">https://www.tiny.cloud/blog/enable-in-app-notifications/</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p><a target="_blank" href="https://www.tiny.cloud/">I publish a monthly newsletter in which</a> I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come ac<a target="_blank" href="https://www.tiny.cloud/">ross while surfing the web.</a></p>
<p><a target="_blank" href="https://www.tiny.cloud/">Connect with</a> <a target="_blank" href="https://www.tiny.cloud/">me through Twitter • LinkedIn • Github or</a> send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[Introducing Ravgeek: Dev Concepts in 60 Seconds]]></title><description><![CDATA[After years of writing code, debugging endlessly, and explaining APIs to teammates over coffee, I’ve finally taken the plunge into something new — bite-sized developer explainers on YouTube.
📺 My new channel is called Ravgeek (“t” dropped from my na...]]></description><link>https://hashnode.ravgeet.in/introducing-ravgeek-dev-concepts-in-60-seconds</link><guid isPermaLink="true">https://hashnode.ravgeet.in/introducing-ravgeek-dev-concepts-in-60-seconds</guid><category><![CDATA[AI]]></category><category><![CDATA[personal development]]></category><category><![CDATA[youtube]]></category><category><![CDATA[content creation]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Wed, 09 Jul 2025 12:32:11 GMT</pubDate><content:encoded><![CDATA[<p>After years of writing code, debugging endlessly, and explaining APIs to teammates over coffee, I’ve finally taken the plunge into something new — <strong>bite-sized developer explainers on YouTube</strong>.</p>
<p>📺 My new channel is called <a target="_blank" href="https://www.youtube.com/@ravgeek"><strong>Ravgeek</strong></a> (“t” dropped from my name)— and it's built around a simple idea:</p>
<blockquote>
<p><strong>Make technical concepts simple, fun, and fast.</strong></p>
</blockquote>
<p>Whether it’s understanding what a REST API is, how Git works, or when to use GraphQL, each video is designed to explain core ideas in <strong>under 60 seconds</strong> — in a way that’s accessible to beginners and still fun for experienced devs.</p>
<p>Here’s a video in which I explain - “What is OAuth”:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=zZKV-w31W7A">https://www.youtube.com/watch?v=zZKV-w31W7A</a></div>
<p> </p>
<p>You’ll see:</p>
<ul>
<li><p>⚡️ Rapid, to-the-point explanations</p>
</li>
<li><p>🎙️ Conversational storytelling (think devs talking over chai)</p>
</li>
<li><p>🎨 Animations, avatars, and a touch of humor</p>
</li>
</ul>
<p>This has been a passion project for me — combining my love for <strong>coding, storytelling, and design</strong> — and I’m excited to finally share it with the world.</p>
<p>👉 Check out the channel: <a target="_blank" href="https://www.youtube.com/@ravgeek">youtube.com/@ravgeek</a><br />💬 And if you like what you see, hit that subscribe button and let me know what topic you'd like me to cover next.</p>
<p>Let’s learn, laugh, and geek out together.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Smart Session Tracker for Your Mac's Menu Bar]]></title><description><![CDATA[Picture this: You sit down at your Mac with a coffee, planning to "quickly check a few emails." Next thing you know, it's 3 PM, your coffee has achieved room temperature, and you're wondering if you've entered some sort of time vortex. Sound familiar...]]></description><link>https://hashnode.ravgeet.in/building-a-smart-session-tracker-for-your-macs-menu-bar</link><guid isPermaLink="true">https://hashnode.ravgeet.in/building-a-smart-session-tracker-for-your-macs-menu-bar</guid><category><![CDATA[Productivity]]></category><category><![CDATA[automation]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Time management]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Wed, 09 Jul 2025 06:30:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752685531619/cf9cd6f6-c307-40c0-94cf-b278297d2689.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Picture this: You sit down at your Mac with a coffee, planning to "quickly check a few emails." Next thing you know, it's 3 PM, your coffee has achieved room temperature, and you're wondering if you've entered some sort of time vortex. Sound familiar?</p>
<p>If you're nodding your head (and possibly rubbing your stiff neck), you're not alone. In our hyper-connected world, time has a sneaky way of slipping through our fingers like sand – or like that last slice of pizza when you're not paying attention.</p>
<p>That's why I built a session tracker that lives right in your Mac's menu bar. It's like having a gentle, persistent friend who reminds you to take breaks, tracks your work patterns, and occasionally judges your life choices (in the nicest possible way).</p>
<h2 id="heading-what-does-this-digital-time-wizard-do">What Does This Digital Time Wizard Do?</h2>
<p>Our session tracker is basically a sophisticated time-keeping ninja that:</p>
<ol>
<li><p><strong>Tracks Your Current Work Session</strong> - It knows when you start working and keeps a running timer</p>
</li>
<li><p><strong>Detects Sleep/Wake Cycles</strong> - Smartly figures out when you've been away from your computer</p>
</li>
<li><p><strong>Logs Daily Activity</strong> - Keeps a record of all your work sessions throughout the day</p>
</li>
<li><p><strong>Sends Gentle Reminders</strong> - Politely suggests you take a break after an hour (because your eyes and back will thank you)</p>
</li>
<li><p><strong>Shows Beautiful Stats</strong> - Displays your current session time and daily totals right in your menu bar</p>
</li>
</ol>
<p>Think of it as a Fitbit for your productivity, but instead of counting steps, it's counting the minutes you spend glued to your screen.</p>
<h2 id="heading-the-magic-behind-the-curtain">The Magic Behind the Curtain</h2>
<p>This isn't just any ordinary timer script – it's a surprisingly sophisticated piece of bash wizardry that handles all sorts of edge cases:</p>
<h3 id="heading-smart-session-detection">Smart Session Detection</h3>
<p>The script doesn't just start counting from when you run it. It's smart enough to:</p>
<ul>
<li><p>Detect when your Mac has been rebooted</p>
</li>
<li><p>Figure out when you've been away (sleeping, lunch break, or that inevitable YouTube rabbit hole)</p>
</li>
<li><p>Resume tracking seamlessly when you return</p>
</li>
</ul>
<h3 id="heading-intelligent-time-gap-detection">Intelligent Time Gap Detection</h3>
<p>Here's where it gets clever: the script monitors for gaps in activity longer than 2 minutes. If it detects you've been away, it logs your previous session and starts a new one. It's like having a personal assistant who's really good at reading between the lines.</p>
<h3 id="heading-cross-reboot-persistence">Cross-Reboot Persistence</h3>
<p>Even if you restart your Mac, the script remembers your previous session and can estimate when it ended. It's like having a time-tracking elephant – it never forgets.</p>
<h2 id="heading-setting-up-your-new-digital-productivity-buddy">Setting Up Your New Digital Productivity Buddy</h2>
<p>Ready to get this bad boy running on your Mac? Here's how to set it up:</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>First, you'll need <strong>xbar</strong> (formerly BitBar), which is a fantastic tool that lets you put the output of any script in your Mac's menu bar:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install xbar using Homebrew</span>
brew install xbar
</code></pre>
<p>If you don't have Homebrew installed, grab it from <a target="_blank" href="http://brew.sh">brew.sh</a> first.</p>
<h3 id="heading-optional-but-recommended-better-notifications">Optional but Recommended: Better Notifications</h3>
<p>For prettier notifications, install <code>terminal-notifier</code>:</p>
<pre><code class="lang-bash">brew install terminal-notifier
</code></pre>
<p>Don't worry if you skip this – the script will fall back to macOS's built-in notification system.</p>
<h3 id="heading-installing-the-script">Installing the Script</h3>
<ol>
<li><strong>Create the xbar plugins directory</strong> (if it doesn't exist):</li>
</ol>
<pre><code class="lang-bash">mkdir -p <span class="hljs-string">"~/Library/ApplicationSupport/xbar/plugins"</span>
</code></pre>
<ol start="2">
<li><strong>Create the script file</strong>:</li>
</ol>
<pre><code class="lang-bash">nano <span class="hljs-string">"~/Library/Application Support/xbar/plugins/current-session.1m.sh"</span>
</code></pre>
<ol start="3">
<li><strong>Copy and paste the following code</strong>:</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Set PATH to include common locations</span>
<span class="hljs-built_in">export</span> PATH=<span class="hljs-string">"/usr/local/bin:/opt/homebrew/bin:<span class="hljs-variable">$PATH</span>"</span>

<span class="hljs-comment"># File to store the session start time</span>
SESSION_FILE=<span class="hljs-string">"/tmp/current_session_start"</span>
<span class="hljs-comment"># File to store daily activity log</span>
DAILY_LOG_FILE=<span class="hljs-string">"/tmp/daily_activity_<span class="hljs-subst">$(date +%Y%m%d)</span>"</span>

<span class="hljs-comment"># Get current time</span>
CURRENT_TIME=$(date +%s)

<span class="hljs-comment"># Check if system was recently awakened by looking at uptime vs session file age</span>
UPTIME_SECONDS=$(sysctl -n kern.boottime | awk <span class="hljs-string">'{print $4}'</span> | sed <span class="hljs-string">'s/,//'</span>)
BOOT_TIME=$(date -r <span class="hljs-string">"<span class="hljs-variable">$UPTIME_SECONDS</span>"</span> +%s 2&gt;/dev/null || <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>)

<span class="hljs-comment"># If session file doesn't exist or is older than boot time, create new session</span>
<span class="hljs-keyword">if</span> [ ! -f <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
  SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>
  SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">true</span>
<span class="hljs-keyword">else</span>
  STORED_TIME=$(cat <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span>)

  <span class="hljs-comment"># Check if the stored time is valid (numeric and reasonable)</span>
  <span class="hljs-keyword">if</span> ! [[ <span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span> =~ ^[0-9]+$ ]] || [ <span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span> -lt 1000000000 ] || [ <span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span> -gt $((CURRENT_TIME + <span class="hljs-number">86400</span>)) ]; <span class="hljs-keyword">then</span>
    <span class="hljs-comment"># Invalid timestamp, start new session</span>
    SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>
    SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">true</span>
  <span class="hljs-keyword">elif</span> [ <span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span> -lt <span class="hljs-string">"<span class="hljs-variable">$BOOT_TIME</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-comment"># Session file is from before last boot, start new session</span>
    SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>
    SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">true</span>
  <span class="hljs-keyword">else</span>
    <span class="hljs-comment"># Check if we've been asleep (gap in timestamps)</span>
    LAST_CHECK_FILE=<span class="hljs-string">"/tmp/last_session_check"</span>
    <span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
      LAST_CHECK=$(cat <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK_FILE</span>"</span>)
      TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

      <span class="hljs-comment"># If gap is more than 2 minutes, assume we were asleep and start new session</span>
      <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$TIME_GAP</span>"</span> -gt 120 ]; <span class="hljs-keyword">then</span>
        SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>
        SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">true</span>
      <span class="hljs-keyword">else</span>
        SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span>
        SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">false</span>
      <span class="hljs-keyword">fi</span>
    <span class="hljs-keyword">else</span>
      SESSION_START=<span class="hljs-string">"<span class="hljs-variable">$STORED_TIME</span>"</span>
      SESSION_FILE_NEEDS_UPDATE=<span class="hljs-literal">false</span>
    <span class="hljs-keyword">fi</span>
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Track daily activity - check if we need to log previous session before updating files</span>
LAST_CHECK_FILE=<span class="hljs-string">"/tmp/last_session_check"</span>
NEW_SESSION_STARTED=<span class="hljs-literal">false</span>

<span class="hljs-comment"># Check if we're about to start a new session and need to log the previous one</span>
<span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK_FILE</span>"</span> ] &amp;&amp; [ -f <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
  LAST_CHECK=$(cat <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK_FILE</span>"</span>)
  PREV_SESSION_START=$(cat <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span>)
  TIME_GAP=$((CURRENT_TIME - LAST_CHECK))

  <span class="hljs-comment"># If gap detected and we have a valid previous session, log it before starting new session</span>
  <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$TIME_GAP</span>"</span> -gt 120 ] &amp;&amp; [ <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_START</span>"</span> -lt <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK</span>"</span> ]; <span class="hljs-keyword">then</span>
    PREV_SESSION_DURATION=$((LAST_CHECK - PREV_SESSION_START))
    <span class="hljs-comment"># Only log sessions longer than 1 minute</span>
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_DURATION</span>"</span> -gt 60 ]; <span class="hljs-keyword">then</span>
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_START</span> <span class="hljs-variable">$LAST_CHECK</span> <span class="hljs-variable">$PREV_SESSION_DURATION</span>"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span>
    <span class="hljs-keyword">fi</span>
    NEW_SESSION_STARTED=<span class="hljs-literal">true</span>
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">elif</span> [ ! -f <span class="hljs-string">"<span class="hljs-variable">$LAST_CHECK_FILE</span>"</span> ] &amp;&amp; [ -f <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-comment"># First run after boot - check if we should log a session from before reboot</span>
  PREV_SESSION_START=$(cat <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span>)
  <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_START</span>"</span> -lt <span class="hljs-string">"<span class="hljs-variable">$BOOT_TIME</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-comment"># Session was from before boot, try to estimate when it ended (use boot time)</span>
    PREV_SESSION_DURATION=$((BOOT_TIME - PREV_SESSION_START))
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_DURATION</span>"</span> -gt 60 ] &amp;&amp; [ <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_DURATION</span>"</span> -lt 86400 ]; <span class="hljs-keyword">then</span>
      <span class="hljs-comment"># Only log if duration seems reasonable (between 1 minute and 24 hours)</span>
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$PREV_SESSION_START</span> <span class="hljs-variable">$BOOT_TIME</span> <span class="hljs-variable">$PREV_SESSION_DURATION</span>"</span> &gt;&gt; <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span>
    <span class="hljs-keyword">fi</span>
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Update session file if we're starting a new session</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE_NEEDS_UPDATE</span>"</span> = <span class="hljs-literal">true</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$SESSION_START</span>"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$SESSION_FILE</span>"</span>
  <span class="hljs-comment"># Reset notification tracking for new session</span>
  rm -f <span class="hljs-string">"/tmp/last_rest_notification"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Update the last check time</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span> &gt; <span class="hljs-string">"/tmp/last_session_check"</span>

<span class="hljs-comment"># Calculate the session duration in seconds</span>
SESSION_DURATION=$((CURRENT_TIME - SESSION_START))

<span class="hljs-comment"># Check if we need to show a rest notification (every hour)</span>
NOTIFICATION_FILE=<span class="hljs-string">"/tmp/last_rest_notification"</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SESSION_DURATION</span>"</span> -gt 3600 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-comment"># Check if we've already shown a notification for this hour</span>
  CURRENT_HOUR=$((SESSION_DURATION / <span class="hljs-number">3600</span>))
  <span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$NOTIFICATION_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
    LAST_NOTIFICATION_HOUR=$(cat <span class="hljs-string">"<span class="hljs-variable">$NOTIFICATION_FILE</span>"</span>)
  <span class="hljs-keyword">else</span>
    LAST_NOTIFICATION_HOUR=0
  <span class="hljs-keyword">fi</span>

  <span class="hljs-comment"># Show notification if we haven't shown one for this hour yet</span>
  <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$CURRENT_HOUR</span>"</span> -gt <span class="hljs-string">"<span class="hljs-variable">$LAST_NOTIFICATION_HOUR</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-comment"># Check if terminal-notifier is available</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">command</span> -v terminal-notifier &gt;/dev/null 2&gt;&amp;1; <span class="hljs-keyword">then</span>
      terminal-notifier -title <span class="hljs-string">"Session Tracker"</span> -subtitle <span class="hljs-string">"Time for a rest"</span> -message <span class="hljs-string">"You've been working for <span class="hljs-variable">${CURRENT_HOUR}</span> hour(s). Consider taking a break!"</span> -sound funk -group <span class="hljs-string">"session-tracker"</span>
    <span class="hljs-keyword">else</span>
      <span class="hljs-comment"># Fallback to osascript if terminal-notifier is not available</span>
      osascript -e <span class="hljs-string">"display notification \"You've been working for <span class="hljs-variable">${CURRENT_HOUR}</span> hour(s). Consider taking a break!\" with title \"Session Tracker\" subtitle \"Time for a rest\" sound name \"Glass\""</span>
    <span class="hljs-keyword">fi</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$CURRENT_HOUR</span>"</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$NOTIFICATION_FILE</span>"</span>
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Calculate total daily activity</span>
TOTAL_TODAY=0
<span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> -r start_time end_time duration; <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">if</span> [ -n <span class="hljs-string">"<span class="hljs-variable">$duration</span>"</span> ] &amp;&amp; [ <span class="hljs-string">"<span class="hljs-variable">$duration</span>"</span> -gt 0 ]; <span class="hljs-keyword">then</span>
      TOTAL_TODAY=$((TOTAL_TODAY + duration))
    <span class="hljs-keyword">fi</span>
  <span class="hljs-keyword">done</span> &lt; <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Add current session to today's total</span>
TOTAL_TODAY=$((TOTAL_TODAY + SESSION_DURATION))

<span class="hljs-comment"># Convert duration to human-readable format</span>
DURATION_HOURS=$((SESSION_DURATION / <span class="hljs-number">3600</span>))
DURATION_MINUTES=$(( (SESSION_DURATION % <span class="hljs-number">3600</span>) / <span class="hljs-number">60</span> ))
DURATION_SECONDS=$((SESSION_DURATION % <span class="hljs-number">60</span>))

<span class="hljs-comment"># Convert total daily time to human-readable format</span>
TOTAL_HOURS=$((TOTAL_TODAY / <span class="hljs-number">3600</span>))
TOTAL_MINUTES=$(( (TOTAL_TODAY % <span class="hljs-number">3600</span>) / <span class="hljs-number">60</span> ))

<span class="hljs-comment"># Format the session output</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-variable">$DURATION_HOURS</span> -gt 0 ]; <span class="hljs-keyword">then</span>
  DURATION_STRING=<span class="hljs-string">"<span class="hljs-variable">${DURATION_HOURS}</span>h <span class="hljs-variable">${DURATION_MINUTES}</span>m"</span>
<span class="hljs-keyword">else</span>
  DURATION_STRING=<span class="hljs-string">"<span class="hljs-variable">${DURATION_MINUTES}</span>m"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Format the daily total output</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-variable">$TOTAL_HOURS</span> -gt 0 ]; <span class="hljs-keyword">then</span>
  TOTAL_STRING=<span class="hljs-string">"<span class="hljs-variable">${TOTAL_HOURS}</span>h <span class="hljs-variable">${TOTAL_MINUTES}</span>m"</span>
<span class="hljs-keyword">else</span>
  TOTAL_STRING=<span class="hljs-string">"<span class="hljs-variable">${TOTAL_MINUTES}</span>m"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Output the session duration and daily total</span>
<span class="hljs-comment"># Add warning indicator if session is over 1 hour</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SESSION_DURATION</span>"</span> -gt 3600 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"⚠️ <span class="hljs-variable">$DURATION_STRING</span> | size=10"</span>
<span class="hljs-keyword">else</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"🕒 <span class="hljs-variable">$DURATION_STRING</span> | size=10"</span>
<span class="hljs-keyword">fi</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📊 Current Session: <span class="hljs-variable">$DURATION_STRING</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"📅 Today Total: <span class="hljs-variable">$TOTAL_STRING</span>"</span>

<span class="hljs-comment"># Show session breakdown if there are previous sessions</span>
<span class="hljs-keyword">if</span> [ -f <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span> ] &amp;&amp; [ -s <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span> ]; <span class="hljs-keyword">then</span>
  SESSION_COUNT=$(wc -l &lt; <span class="hljs-string">"<span class="hljs-variable">$DAILY_LOG_FILE</span>"</span>)
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"🔢 Sessions Today: <span class="hljs-subst">$((SESSION_COUNT + 1)</span>)"</span>
<span class="hljs-keyword">fi</span>

<span class="hljs-comment"># Add rest reminder in dropdown if session is over 1 hour</span>
<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SESSION_DURATION</span>"</span> -gt 3600 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"⏰ Take a break! You've been working for over an hour | color=orange"</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<ol start="4">
<li><strong>Make it executable</strong>:</li>
</ol>
<pre><code class="lang-bash">chmod +x <span class="hljs-string">"~/Library/Application Support/xbar/plugins/current-session.1m.sh"</span>
</code></pre>
<ol start="5">
<li><p><strong>Start xbar and refresh</strong>:</p>
<ul>
<li><p>Launch xbar from your Applications folder</p>
</li>
<li><p>Click the xbar icon in your menu bar and select "Refresh all"</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-understanding-the-code-a-gentle-journey-through-bash-land">Understanding the Code: A Gentle Journey Through Bash Land</h2>
<p>Let's break down what this script does, step by step:</p>
<h3 id="heading-the-setup-phase">The Setup Phase</h3>
<pre><code class="lang-bash">SESSION_FILE=<span class="hljs-string">"/tmp/current_session_start"</span>
DAILY_LOG_FILE=<span class="hljs-string">"/tmp/daily_activity_<span class="hljs-subst">$(date +%Y%m%d)</span>"</span>
</code></pre>
<p>We store our session data in temporary files. The daily log file includes the date, so each day gets its own log file. It's like having a new diary page for each day!</p>
<h3 id="heading-the-detective-work">The Detective Work</h3>
<pre><code class="lang-bash">UPTIME_SECONDS=$(sysctl -n kern.boottime | awk <span class="hljs-string">'{print $4}'</span> | sed <span class="hljs-string">'s/,//'</span>)
BOOT_TIME=$(date -r <span class="hljs-string">"<span class="hljs-variable">$UPTIME_SECONDS</span>"</span> +%s 2&gt;/dev/null || <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$CURRENT_TIME</span>"</span>)
</code></pre>
<p>This is where we get all CSI about when your Mac was last booted. We use this to figure out if your session file is from before a reboot.</p>
<h3 id="heading-the-smart-session-logic">The Smart Session Logic</h3>
<p>The script has several scenarios it handles:</p>
<ol>
<li><p><strong>First run ever</strong>: Creates a new session</p>
</li>
<li><p><strong>File exists but invalid</strong>: Starts fresh (protects against corrupted data)</p>
</li>
<li><p><strong>File is from before reboot</strong>: Starts a new session post-reboot</p>
</li>
<li><p><strong>Checking for sleep gaps</strong>: If there's a gap &gt; 2 minutes, it assumes you were away</p>
</li>
</ol>
<h3 id="heading-the-notification-system">The Notification System</h3>
<pre><code class="lang-bash"><span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$SESSION_DURATION</span>"</span> -gt 3600 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-comment"># Time for a break notification logic</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<p>After an hour of work, the script gently reminds you to take a break. It's like having a caring friend who's also really good at math.</p>
<h2 id="heading-what-youll-see-in-action">What You'll See in Action</h2>
<p>Once everything is running, you'll see:</p>
<ul>
<li><p><strong>🕒 45m</strong> in your menu bar (showing your current session time)</p>
</li>
<li><p><strong>⚠️ 1h 23m</strong> when you've been working for over an hour (subtle hint to take a break)</p>
</li>
<li><p>A dropdown menu showing:</p>
<ul>
<li><p>📊 Current Session: 1h 23m</p>
</li>
<li><p>📅 Today Total: 3h 45m</p>
</li>
<li><p>🔢 Sessions Today: 4</p>
</li>
<li><p>⏰ Take a break! reminder (if you've been working too long)</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-the-beauty-of-simplicity">The Beauty of Simplicity</h2>
<p>What I love about this solution is that it's:</p>
<ul>
<li><p><strong>Lightweight</strong>: Uses minimal system resources</p>
</li>
<li><p><strong>Unobtrusive</strong>: Sits quietly in your menu bar</p>
</li>
<li><p><strong>Smart</strong>: Handles edge cases you didn't even think about</p>
</li>
<li><p><strong>Customizable</strong>: Easy to modify for your specific needs</p>
</li>
</ul>
<h2 id="heading-customization-ideas">Customization Ideas</h2>
<p>Want to make it your own? Here are some ideas:</p>
<ol>
<li><p><strong>Change the break reminder interval</strong>: Modify the <code>3600</code> seconds (1 hour) to whatever works for you</p>
</li>
<li><p><strong>Add different notification sounds</strong>: Change the <code>sound funk</code> to <code>sound glass</code>, <code>sound ping</code>, etc.</p>
</li>
<li><p><strong>Modify the time gap detection</strong>: Change the <code>120</code> seconds gap threshold</p>
</li>
<li><p><strong>Add more detailed logging</strong>: Include timestamps, session names, or project tags</p>
</li>
</ol>
<h2 id="heading-the-philosophical-side-why-this-matters">The Philosophical Side: Why This Matters</h2>
<p>In our always-on world, this simple script serves a deeper purpose. It's not just about tracking time – it's about being mindful of how we spend our most precious resource. By making our work patterns visible, we can:</p>
<ul>
<li><p><strong>Recognize unhealthy patterns</strong> (like those 4-hour coding binges)</p>
</li>
<li><p><strong>Celebrate productivity</strong> (look at all those completed sessions!)</p>
</li>
<li><p><strong>Build better habits</strong> (those break reminders really do help)</p>
</li>
<li><p><strong>Understand our rhythms</strong> (maybe you're most productive in the morning?)</p>
</li>
</ul>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Building this session tracker was a fun exercise in bash scripting and practical problem-solving. It started as a simple "how long have I been working?" question and evolved into a surprisingly sophisticated time-tracking system.</p>
<p>The best part? It's taught me to actually take breaks. Turns out, when your computer politely suggests you step away from the screen, you're more likely to listen than when your back is screaming at you.</p>
<p>So go ahead, give it a try! Your future self (and your neck) will thank you. And who knows? You might just discover that you're either more or less productive than you thought. Either way, at least you'll know for sure.</p>
]]></content:encoded></item><item><title><![CDATA[Building a Real-Time CPU Monitor for macOS with xbar]]></title><description><![CDATA[Have you ever noticed your Mac's fan spinning wildly but couldn't quickly identify which process was consuming all your CPU?
As a developer who uses Visual Studio Code daily, I rely heavily on its rich ecosystem of extensions to boost my productivity...]]></description><link>https://hashnode.ravgeet.in/building-a-real-time-cpu-monitor-for-macos-with-xbar</link><guid isPermaLink="true">https://hashnode.ravgeet.in/building-a-real-time-cpu-monitor-for-macos-with-xbar</guid><category><![CDATA[Productivity]]></category><category><![CDATA[General Programming]]></category><category><![CDATA[macOS]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Fri, 04 Jul 2025 08:47:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751618719871/6e4b910d-06b5-4166-bf21-acd18e159db7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever noticed your Mac's fan spinning wildly but couldn't quickly identify which process was consuming all your CPU?</p>
<p>As a developer who uses Visual Studio Code daily, I rely heavily on its rich ecosystem of extensions to boost my productivity. But over time, I started noticing my MacBook Air heating up, the fans spinning loudly, and my system becoming sluggish — all while I was just editing code. When I opened the Activity Monitor, I saw one or more mysterious "Code Helper (Plugin)" processes consuming 90–100% CPU, but there was no clear indication of which extension was responsible.</p>
<p>VS Code spawns multiple helper processes, and most of them are generically named, making it incredibly difficult to trace high CPU usage back to a specific extension. This left me guessing — was it Copilot? ESLint? Live Server? I needed a way to monitor these extensions intelligently, without sacrificing performance or productivity.</p>
<p>That's what led me to build a solution — a lightweight tool that monitors CPU usage in real time, maps it to the responsible extension, and alerts me before things spiral out of control. Today, I'll walk you through building a lightweight, real-time CPU monitoring tool that lives in your macOS menu bar and sends notifications when processes exceed your defined thresholds.</p>
<h2 id="heading-what-were-building">What We're Building</h2>
<p>Our CPU monitor will:</p>
<ul>
<li><p>Display CPU status directly in the menu bar</p>
</li>
<li><p>Alert you when any process exceeds 80% CPU usage</p>
</li>
<li><p>Send native macOS notifications for high CPU processes</p>
</li>
<li><p>Provide special handling for VS Code extensions and helpers</p>
</li>
<li><p>Show detailed process information (PID, name, command)</p>
</li>
<li><p>Indicate when all processes are running normally</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we start, you'll need:</p>
<ul>
<li><p>macOS (this guide is macOS-specific)</p>
</li>
<li><p><a target="_blank" href="https://xbarapp.com/">xbar</a> installed (formerly BitBar)</p>
</li>
<li><p>Basic familiarity with shell scripting</p>
</li>
</ul>
<h2 id="heading-the-architecture">The Architecture</h2>
<p>Our solution uses a simple but effective approach:</p>
<ol>
<li><p><strong>xbar Integration</strong>: The script runs every 5 minutes (indicated by the <code>.5m.</code> in the filename)</p>
</li>
<li><p><strong>Process Monitoring</strong>: We use <code>ps</code> to capture all running processes with their CPU usage</p>
</li>
<li><p><strong>Threshold Detection</strong>: Any process using more than 80% CPU triggers an alert</p>
</li>
<li><p><strong>Native Notifications</strong>: We leverage macOS's <code>osascript</code> for system notifications</p>
</li>
<li><p><strong>Fallback Support</strong>: Includes support for <code>terminal-notifier</code> as a backup</p>
</li>
</ol>
<h2 id="heading-the-complete-script">The Complete Script</h2>
<p>Here's the full <a target="_blank" href="http://vscode-ext-monitor.5m.sh"><code>vscode-ext-monitor.5m.sh</code></a> script:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

CPU_THRESHOLD=80.0
HIGH_CPU_FOUND=0

<span class="hljs-comment"># Menu Bar Title</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"🖥️ CPU Monitor"</span>

<span class="hljs-comment"># Store process information in a temporary file to avoid subshell issues</span>
TEMP_FILE=$(mktemp)
ps -Ao pid,%cpu,<span class="hljs-built_in">command</span> | grep -v <span class="hljs-string">"ps -Ao"</span> | grep -v grep &gt; <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>

<span class="hljs-comment"># Read from the temporary file</span>
<span class="hljs-keyword">while</span> IFS= <span class="hljs-built_in">read</span> -r line; <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">$line</span>"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">continue</span>
  <span class="hljs-keyword">fi</span>

  cpu=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$line</span>"</span> | awk <span class="hljs-string">'{print $2}'</span>)
  pid=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$line</span>"</span> | awk <span class="hljs-string">'{print $1}'</span>)
  <span class="hljs-built_in">command</span>=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$line</span>"</span> | cut -d <span class="hljs-string">' '</span> -f3-)

  <span class="hljs-comment"># Skip if CPU is not a valid number or is 0.0</span>
  <span class="hljs-keyword">if</span> ! <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$cpu</span>"</span> | grep -q <span class="hljs-string">'^[0-9]*\.[0-9]*$'</span> || [ <span class="hljs-string">"<span class="hljs-variable">$cpu</span>"</span> = <span class="hljs-string">"0.0"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">continue</span>
  <span class="hljs-keyword">fi</span>

  is_high=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$cpu</span> &gt; <span class="hljs-variable">$CPU_THRESHOLD</span>"</span> | bc)

  <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$is_high</span>"</span> -eq 1 ]; <span class="hljs-keyword">then</span>
    HIGH_CPU_FOUND=1
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"⚠️ High CPU (<span class="hljs-variable">$cpu</span>%)"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"PID: <span class="hljs-variable">$pid</span>"</span>

    <span class="hljs-comment"># Get process name from command</span>
    process_name=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$command</span>"</span> | awk <span class="hljs-string">'{print $1}'</span> | xargs basename 2&gt;/dev/null || <span class="hljs-built_in">echo</span> <span class="hljs-string">"Unknown"</span>)
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"📱 Process: <span class="hljs-variable">$process_name</span>"</span>

    <span class="hljs-comment"># Show truncated command</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"💻 <span class="hljs-variable">${command:0:60}</span>..."</span>

    notification_title=<span class="hljs-string">"From CPU Monitor"</span>

    <span class="hljs-comment"># Special handling for different process types</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$command</span>"</span> | grep -q <span class="hljs-string">".vscode/extensions"</span>; <span class="hljs-keyword">then</span>
      ext=$(<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$command</span>"</span> | grep -o <span class="hljs-string">"/Users/[^ ]*\.vscode/extensions/[^ ]*"</span>)
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"🧩 VS Code Extension: <span class="hljs-subst">$(basename <span class="hljs-string">"<span class="hljs-variable">$ext</span>"</span>)</span>"</span>
      notification_message=<span class="hljs-string">"High CPU: <span class="hljs-variable">${cpu}</span>% by VS Code extension <span class="hljs-subst">$(basename <span class="hljs-string">"<span class="hljs-variable">$ext</span>"</span>)</span>"</span>
    <span class="hljs-keyword">elif</span> <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$command</span>"</span> | grep -q <span class="hljs-string">"Code Helper"</span>; <span class="hljs-keyword">then</span>
      <span class="hljs-built_in">echo</span> <span class="hljs-string">"🔧 VS Code Helper Process"</span>
      notification_message=<span class="hljs-string">"High CPU: <span class="hljs-variable">${cpu}</span>% by VS Code Helper"</span>
    <span class="hljs-keyword">else</span>
      notification_message=<span class="hljs-string">"High CPU: <span class="hljs-variable">${cpu}</span>% by <span class="hljs-variable">$process_name</span>"</span>
    <span class="hljs-keyword">fi</span>

    <span class="hljs-comment"># Desktop notification (macOS only) - with error handling</span>
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">command</span> -v osascript &gt;/dev/null 2&gt;&amp;1; <span class="hljs-keyword">then</span>
      osascript -e <span class="hljs-string">"display notification \"<span class="hljs-variable">$notification_message</span>\" with title \"<span class="hljs-variable">$notification_title</span>\""</span> 2&gt;/dev/null || {
        <span class="hljs-comment"># Fallback: try using terminal-notifier if available</span>
        <span class="hljs-keyword">if</span> <span class="hljs-built_in">command</span> -v terminal-notifier &gt;/dev/null 2&gt;&amp;1; <span class="hljs-keyword">then</span>
          terminal-notifier -title <span class="hljs-string">"<span class="hljs-variable">$notification_title</span>"</span> -message <span class="hljs-string">"<span class="hljs-variable">$notification_message</span>"</span> 2&gt;/dev/null
        <span class="hljs-keyword">fi</span>
      }
    <span class="hljs-keyword">fi</span>
  <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">done</span> &lt; <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>

<span class="hljs-comment"># Clean up temporary file</span>
rm -f <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>

<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$HIGH_CPU_FOUND</span>"</span> -eq 0 ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"✅ All processes under <span class="hljs-variable">${CPU_THRESHOLD}</span>%"</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<h2 id="heading-key-technical-decisions">Key Technical Decisions</h2>
<h3 id="heading-1-avoiding-subshell-issues">1. Avoiding Subshell Issues</h3>
<p>Initially, we faced a common bash pitfall where notifications wouldn't work because the <code>while</code> loop was running in a subshell:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># This doesn't work for GUI operations</span>
ps ... | <span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> line; <span class="hljs-keyword">do</span>
  osascript -e <span class="hljs-string">"display notification ..."</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p><strong>Solution</strong>: We use a temporary file approach to avoid the subshell:</p>
<pre><code class="lang-bash">TEMP_FILE=$(mktemp)
ps -Ao pid,%cpu,<span class="hljs-built_in">command</span> &gt; <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>
<span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> line; <span class="hljs-keyword">do</span>
  <span class="hljs-comment"># Process data and send notifications</span>
<span class="hljs-keyword">done</span> &lt; <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>
rm -f <span class="hljs-string">"<span class="hljs-variable">$TEMP_FILE</span>"</span>
</code></pre>
<h3 id="heading-2-robust-cpu-validation">2. Robust CPU Validation</h3>
<p>We validate CPU values to ensure we're working with actual numeric data:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">if</span> ! <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$cpu</span>"</span> | grep -q <span class="hljs-string">'^[0-9]*\.[0-9]*$'</span> || [ <span class="hljs-string">"<span class="hljs-variable">$cpu</span>"</span> = <span class="hljs-string">"0.0"</span> ]; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">continue</span>
<span class="hljs-keyword">fi</span>
</code></pre>
<p>This prevents errors from malformed process data.</p>
<h3 id="heading-3-smart-process-classification">3. Smart Process Classification</h3>
<p>The script intelligently categorizes processes:</p>
<ul>
<li><p><strong>VS Code Extensions</strong>: Detected by <code>.vscode/extensions</code> in the command path</p>
</li>
<li><p><strong>VS Code Helpers</strong>: Identified by "Code Helper" in the command</p>
</li>
<li><p><strong>General Processes</strong>: Everything else gets generic handling</p>
</li>
</ul>
<h3 id="heading-4-notification-reliability">4. Notification Reliability</h3>
<p>We implement a two-tier notification system:</p>
<pre><code class="lang-bash">osascript -e <span class="hljs-string">"display notification ..."</span> 2&gt;/dev/null || {
  <span class="hljs-comment"># Fallback to terminal-notifier if available</span>
  <span class="hljs-keyword">if</span> <span class="hljs-built_in">command</span> -v terminal-notifier &gt;/dev/null 2&gt;&amp;1; <span class="hljs-keyword">then</span>
    terminal-notifier -title <span class="hljs-string">"..."</span> -message <span class="hljs-string">"..."</span>
  <span class="hljs-keyword">fi</span>
}
</code></pre>
<h2 id="heading-installation-and-setup">Installation and Setup</h2>
<ol>
<li><p><strong>Install xbar</strong> if you haven't already:</p>
<pre><code class="lang-bash"> brew install --cask xbar
</code></pre>
</li>
<li><p><strong>Create the plugin directory</strong>:</p>
<pre><code class="lang-bash"> mkdir -p <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/Library/Application Support/xbar/plugins"</span>
</code></pre>
</li>
<li><p><strong>Save the script</strong> as <a target="_blank" href="http://vscode-ext-monitor.5m.sh"><code>vscode-ext-monitor.5m.sh</code></a> in the plugins directory</p>
</li>
<li><p><strong>Make it executable</strong>:</p>
<pre><code class="lang-bash"> chmod +x <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/Library/Application Support/xbar/plugins/vscode-ext-monitor.5m.sh"</span>
</code></pre>
</li>
<li><p><strong>Launch xbar</strong> and refresh to see your new CPU monitor</p>
</li>
</ol>
<h2 id="heading-customization-options">Customization Options</h2>
<h3 id="heading-adjust-the-cpu-threshold">Adjust the CPU Threshold</h3>
<p>Change the threshold by modifying this line:</p>
<pre><code class="lang-bash">CPU_THRESHOLD=80.0  <span class="hljs-comment"># Change to your preferred percentage</span>
</code></pre>
<h3 id="heading-modify-the-update-frequency">Modify the Update Frequency</h3>
<p>Rename the file to change how often it runs:</p>
<ul>
<li><p><code>.</code><a target="_blank" href="http://1m.sh"><code>1m.sh</code></a> = Every minute</p>
</li>
<li><p><code>.</code><a target="_blank" href="http://30s.sh"><code>30s.sh</code></a> = Every 30 seconds</p>
</li>
<li><p><code>.</code><a target="_blank" href="http://10m.sh"><code>10m.sh</code></a> = Every 10 minutes</p>
</li>
</ul>
<h3 id="heading-add-more-process-types">Add More Process Types</h3>
<p>Extend the classification logic:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">elif</span> <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$command</span>"</span> | grep -q <span class="hljs-string">"chrome"</span>; <span class="hljs-keyword">then</span>
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"🌐 Chrome Process"</span>
  notification_message=<span class="hljs-string">"High CPU: <span class="hljs-variable">${cpu}</span>% by Chrome"</span>
</code></pre>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<h3 id="heading-notifications-not-appearing">Notifications Not Appearing?</h3>
<ol>
<li><p><strong>Check System Preferences</strong>: Ensure notifications are enabled for Terminal in System Preferences &gt; Notifications &amp; Focus</p>
</li>
<li><p><strong>Test manually</strong>:</p>
<pre><code class="lang-bash"> osascript -e <span class="hljs-string">'display notification "Test" with title "Test"'</span>
</code></pre>
</li>
<li><p><strong>Install terminal-notifier as backup</strong>:</p>
<pre><code class="lang-bash"> brew install terminal-notifier
</code></pre>
</li>
</ol>
<h3 id="heading-script-not-running">Script Not Running?</h3>
<ol>
<li><p><strong>Verify file permissions</strong>:</p>
<pre><code class="lang-bash"> ls -la <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/Library/Application Support/xbar/plugins/"</span>
</code></pre>
</li>
<li><p><strong>Check xbar is running</strong> and refresh the menu</p>
</li>
<li><p><strong>Test the script manually</strong>:</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/Library/Application Support/xbar/plugins/"</span>
 ./vscode-ext-monitor.5m.sh
</code></pre>
</li>
</ol>
<h2 id="heading-whats-next">What's Next?</h2>
<p>This CPU monitor provides a solid foundation that you can extend further:</p>
<ul>
<li><p><strong>Memory Monitoring</strong>: Add RAM usage alerts</p>
</li>
<li><p><strong>Network Activity</strong>: Monitor processes with high network usage</p>
</li>
<li><p><strong>Historical Tracking</strong>: Log high CPU events to a file</p>
</li>
<li><p><strong>Kill Process Feature</strong>: Add menu options to terminate problematic processes</p>
</li>
<li><p><strong>Custom Thresholds</strong>: Different thresholds for different process types</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building system monitoring tools doesn't require complex frameworks or heavy applications. With a simple bash script and xbar, we've created a lightweight, effective CPU monitor that:</p>
<ul>
<li><p>Provides real-time visibility into system performance</p>
</li>
<li><p>Sends proactive notifications before problems escalate</p>
</li>
<li><p>Offers detailed process information for quick troubleshooting</p>
</li>
<li><p>Runs efficiently with minimal system overhead</p>
</li>
</ul>
<p>The beauty of this approach is its simplicity and customizability. You have full control over the monitoring logic, notification behavior, and display format. Plus, since it's just a bash script, you can easily modify it to suit your specific needs.</p>
<p><em>This CPU monitor has been tested on macOS Sequoia and later. The script should work on earlier versions but may require minor adjustments for notification handling.</em></p>
]]></content:encoded></item><item><title><![CDATA[Automate GitHub stats reporting with scheduled pipelines]]></title><description><![CDATA[Release notes provide essential documentation when a new software version is released. For release notes to be most effective, dev teams must consolidate all of the work that has been done since the previous release. It is a hectic task that requires...]]></description><link>https://hashnode.ravgeet.in/automate-github-stats-reporting-with-scheduled-pipelines</link><guid isPermaLink="true">https://hashnode.ravgeet.in/automate-github-stats-reporting-with-scheduled-pipelines</guid><category><![CDATA[General Programming]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Fri, 04 Jul 2025 06:30:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750427972230/9fcac32e-9825-4651-ada6-784f5ba017f8.avif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Release notes provide essential documentation when a new software version is released. For release notes to be most effective, dev teams must consolidate all of the work that has been done since the previous release. It is a hectic task that requires a lot of effort and time sorting through weeks or even months of software issues and pull requests.</p>
<p>Why not make the life of the release team easier by automating the creation of release notes? You can, using a combination of GitHub API and a <a target="_blank" href="https://circleci.com/blog/what-is-a-ci-cd-pipeline/">CI/CD tool</a> like CircleCI. Automate the task of fetching issues and pull requests, and put them in a single place where they can be accessed easily by the release notes team.</p>
<p>In this tutorial, you’ll learn to use the GitHub API and CircleCI to create weekly stats for your GitHub repositories. The plan is to build an automated workflow using CircleCI <a target="_blank" href="https://circleci.com/blog/using-scheduled-pipelines/">scheduled pipelines</a>. The pipeline will fetch all the issues and pull requests made during a specified interval, save these stats in a file, and commit this file back to the repository.</p>
<p>Read the full blog on <a target="_blank" href="https://circleci.com/blog/automate-github-stats/">CircleCI</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://circleci.com/blog/automate-github-stats/">https://circleci.com/blog/automate-github-stats/</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p>I publish a <a target="_blank" href="https://www.ravsam.in/newsletter/">monthly newsletter</a> in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.</p>
<p>Connect with me through <a target="_blank" href="https://twitter.com/ravgeetdhillon">Twitter</a> • <a target="_blank" href="https://linkedin.com/in/ravgeetdhillon">LinkedIn</a> • <a target="_blank" href="https://github.com/ravgeetdhillon">Github</a> or send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[Getting the Most Out of GitHub Copilot Chat in VS Code]]></title><description><![CDATA[GitHub Copilot is already an incredible tool for autocompleting code, but if you haven’t tried Copilot Chat, you’re missing out on one of the most powerful AI developer workflows available today.
In this post, you’ll walk through:

How to use Copilot...]]></description><link>https://hashnode.ravgeet.in/getting-the-most-out-of-github-copilot-chat-in-vs-code</link><guid isPermaLink="true">https://hashnode.ravgeet.in/getting-the-most-out-of-github-copilot-chat-in-vs-code</guid><category><![CDATA[General Programming]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Developer]]></category><category><![CDATA[AI]]></category><category><![CDATA[copilot]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Mon, 30 Jun 2025 18:30:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nbZHM2uwkJs/upload/9c2c6c473ad9dfe9cc28dc9271647775.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GitHub Copilot is already an incredible tool for autocompleting code, but if you haven’t tried <strong>Copilot Chat</strong>, you’re missing out on one of the most powerful AI developer workflows available today.</p>
<p>In this post, you’ll walk through:</p>
<ul>
<li><p>How to use Copilot Chat in VS Code</p>
</li>
<li><p>What it’s best at</p>
</li>
<li><p>Real-world prompts you can use right away</p>
</li>
</ul>
<h2 id="heading-what-is-github-copilot-chat">What Is GitHub Copilot Chat?</h2>
<p>Copilot Chat brings the power of ChatGPT <strong>directly into VS Code</strong>, allowing you to ask natural language questions about your code, request explanations, generate tests, fix bugs, refactor logic, and more — without ever leaving your editor.</p>
<p>It’s like having an AI pair programmer that:</p>
<ul>
<li><p>Understands your code context</p>
</li>
<li><p>Works inline or in a chat panel</p>
</li>
<li><p>Helps you learn, debug, and ship faster</p>
</li>
</ul>
<h2 id="heading-what-can-you-use-copilot-chat-for">What Can You Use Copilot Chat For?</h2>
<p>Here are some powerful use cases:</p>
<h3 id="heading-code-understanding-amp-explanation">Code Understanding &amp; Explanation</h3>
<p>You can highlight any block of code and ask:</p>
<ul>
<li><p>“Explain what this code does”</p>
</li>
<li><p>“What is the time complexity here?”</p>
</li>
<li><p>“Why am I getting this TypeError?”</p>
</li>
</ul>
<h3 id="heading-code-improvement-amp-refactoring">Code Improvement &amp; Refactoring</h3>
<p>Ask it to:</p>
<ul>
<li><p>“Refactor this function to be cleaner”</p>
</li>
<li><p>“Optimize this loop”</p>
</li>
<li><p>“Suggest better variable names”</p>
</li>
</ul>
<h3 id="heading-test-generation">Test Generation</h3>
<p>Save hours writing boilerplate with prompts like:</p>
<ul>
<li><p>“Write unit tests for this function using Jest”</p>
</li>
<li><p>“Generate test cases for edge inputs”</p>
</li>
</ul>
<h3 id="heading-code-conversion-amp-migration">Code Conversion &amp; Migration</h3>
<p>Let it help with transitions like:</p>
<ul>
<li><p>“Convert this JS code to TypeScript”</p>
</li>
<li><p>“Rewrite this to use async/await”</p>
</li>
<li><p>“Switch from useEffect to React Query”</p>
</li>
</ul>
<h3 id="heading-general-cleanup">General Cleanup</h3>
<p>Ask it to:</p>
<ul>
<li><p>“Remove unused imports”</p>
</li>
<li><p>“Simplify this logic”</p>
</li>
<li><p>“Make this more readable”</p>
</li>
</ul>
<h2 id="heading-some-example-prompts-you-can-use-right-now">Some Example Prompts You Can Use Right Now</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Use Case</td><td>Example Prompt</td></tr>
</thead>
<tbody>
<tr>
<td>Understand Code</td><td><code>Explain this function step by step</code></td></tr>
<tr>
<td>Improve Performance</td><td><code>Optimize this loop for large datasets</code></td></tr>
<tr>
<td>Refactor Logic</td><td><code>Make this code more readable and concise</code></td></tr>
<tr>
<td>Convert Language</td><td><code>Translate this from Python to JavaScript</code></td></tr>
<tr>
<td>Debug Errors</td><td><code>Fix the error in this fetch call</code></td></tr>
<tr>
<td>Generate Tests</td><td><code>Write unit tests for this React component</code></td></tr>
<tr>
<td>Learn Concepts</td><td><code>What is useMemo in React and when to use it?</code></td></tr>
<tr>
<td>Clean Up Code</td><td><code>Remove all unused variables from this file</code></td></tr>
</tbody>
</table>
</div><blockquote>
<p>✅ Tip: You can highlight a code block and then open Copilot Chat to get smarter, context-aware answers.</p>
</blockquote>
<h2 id="heading-how-to-enable-copilot-chat-in-vs-code">How to Enable Copilot Chat in VS Code</h2>
<ol>
<li><p>Install the GitHub Copilot Chat extension from the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat">VS Code Marketplace</a></p>
</li>
<li><p>Make sure you’re signed in to GitHub with an active Copilot plan</p>
</li>
<li><p>Open the Copilot Chat panel or use <code>Cmd/Ctrl + I</code> to start inline chat</p>
</li>
<li><p>Highlight code and right-click → <strong>Ask Copilot</strong></p>
</li>
</ol>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Copilot Chat takes the promise of AI-assisted development to the next level. It’s not just about writing code faster — it’s about <strong>thinking through problems, debugging more efficiently, and learning as you go</strong>, all inside your editor.</p>
]]></content:encoded></item><item><title><![CDATA[Generating dynamic sales quotes with Dropbox Sign]]></title><description><![CDATA[Creating and sending price quotes is a necessary part of business, but it can also be a laborious process, especially if the sales team sends out multiple quotes daily. You can make things easier on yourself and your sales team by automating your sal...]]></description><link>https://hashnode.ravgeet.in/generating-dynamic-sales-quotes-with-dropbox-sign</link><guid isPermaLink="true">https://hashnode.ravgeet.in/generating-dynamic-sales-quotes-with-dropbox-sign</guid><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Thu, 26 Jun 2025 06:30:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750428177112/383145d9-50e5-4cfd-9f1b-1c031a4117e6.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Creating and sending price quotes is a necessary part of business, but it can also be a laborious process, especially if the sales team sends out multiple quotes daily. You can make things easier on yourself and your sales team by automating your sales quote generation. An excellent way to do that is by using the <a target="_blank" href="https://sign.dropbox.com/developers"><strong>Dropbox Sign API</strong>.</a></p>
<p><a target="_blank" href="https://sign.dropbox.com/developers">Dropbox Sign</a> allows you to create and send bulk templates, as well as generate dynamic documents such as sales quotes. In this tutorial, you’ll create a command-line utility for generating sales quote documents using the Dropbox Sign API. You’ll pass input data as command line arguments, creating emailed sales quote documents that customers can sign digitally.</p>
<p>Read the full blog on <a target="_blank" href="https://sign.dropbox.com/blog/generating-dynamic-sales-quotes">Dropbox</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://sign.dropbox.com/blog/generating-dynamic-sales-quotes">https://sign.dropbox.com/blog/generating-dynamic-sales-quotes</a></div>
<p> </p>
<p>Thanks for reading 💜</p>
<hr />
<p>I publish a <a target="_blank" href="https://www.ravsam.in/newsletter/">monthly newsletter</a> in which I share personal stories, things that I am working on, what is happening in the world of tech, and some interesting dev-related posts which I come across while surfing the web.</p>
<p>Connect with me through <a target="_blank" href="https://twitter.com/ravgeetdhillon">Twitter</a> • <a target="_blank" href="https://linkedin.com/in/ravgeetdhillon">LinkedIn</a> • <a target="_blank" href="https://github.com/ravgeetdhillon">Github</a> or send me an <a target="_blank" href="mailto:ravgeetdhillon@gmail.com">Email</a>.</p>
<p>— <a target="_blank" href="https://www.ravgeet.in/">Ravgeet</a>, <em>Full Stack Developer and Technical Content Writer</em></p>
]]></content:encoded></item><item><title><![CDATA[Improve Table Speed in React by Using Web Workers for Filters]]></title><description><![CDATA[TL;DR: I implemented a Web Worker–powered filtering system in a React data table component to eliminate UI lag and improve responsiveness when working with large datasets. Here's how and why.

The Problem: Filtering Slows the UI
Our application relie...]]></description><link>https://hashnode.ravgeet.in/improve-table-speed-in-react-by-using-web-workers-for-filters</link><guid isPermaLink="true">https://hashnode.ravgeet.in/improve-table-speed-in-react-by-using-web-workers-for-filters</guid><category><![CDATA[General Programming]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[performance]]></category><category><![CDATA[React]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Thu, 19 Jun 2025 15:33:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/yaK5It0P0Gc/upload/483ed162f5c50831e986593bdbaed484.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>TL;DR: I implemented a <strong>Web Worker–powered filtering system</strong> in a React data table component to eliminate UI lag and improve responsiveness when working with large datasets. Here's how and why.</p>
</blockquote>
<h2 id="heading-the-problem-filtering-slows-the-ui">The Problem: Filtering Slows the UI</h2>
<p>Our application relies heavily on a custom <code>&lt;NewTable /&gt;</code> component that supports:</p>
<ul>
<li><p>Nested (hierarchical) rows</p>
</li>
<li><p>Column-based filtering</p>
</li>
<li><p>Custom filters</p>
</li>
<li><p>Server-side pagination</p>
</li>
</ul>
<p>As datasets grew into the thousands of rows, filtering became noticeably sluggish. The culprit? All filtering logic ran on the <strong>main UI thread</strong>, blocking React’s render cycle and causing the interface to freeze temporarily.</p>
<h2 id="heading-goal">Goal</h2>
<p>Move heavy data-filtering logic off the main thread using <strong>Web Workers</strong>, without breaking existing functionality or developer experience.</p>
<h2 id="heading-the-solution-asynchronous-filtering-with-web-workers">The Solution: Asynchronous Filtering with Web Workers</h2>
<p>We built a pipeline that:</p>
<ol>
<li><p><strong>Serializes hierarchical data</strong> into a flat structure</p>
</li>
<li><p>Sends that data to a <strong>dedicated Web Worker</strong></p>
</li>
<li><p>Worker filters based on column &amp; custom filters</p>
</li>
<li><p>Sends back the result</p>
</li>
<li><p>Updates the UI reactively and shows a loader while waiting</p>
</li>
</ol>
<h2 id="heading-implementation-breakdown">Implementation Breakdown</h2>
<h3 id="heading-1-set-up-searchworkerjs">1. Set up <code>search.worker.js</code></h3>
<p>We created a Web Worker dynamically using a blob:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> searchWorker = <span class="hljs-function">() =&gt;</span> {
  onmessage = <span class="hljs-keyword">async</span> (e) =&gt; {
    importScripts(<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"</span>);
    importScripts(<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.7/dayjs.min.js"</span>);

    <span class="hljs-comment">// ... logic for deep filtering, date handling, and custom filters ...</span>

    postMessage({ success, result });
  };
};

<span class="hljs-keyword">let</span> code = searchWorker.toString();
code = code.substring(code.indexOf(<span class="hljs-string">"{"</span>) + <span class="hljs-number">1</span>, code.lastIndexOf(<span class="hljs-string">"}"</span>));
<span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob([code], { <span class="hljs-keyword">type</span>: <span class="hljs-string">"application/javascript"</span> });
<span class="hljs-keyword">const</span> workerScript = URL.createObjectURL(blob);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> workerScript;
</code></pre>
<h3 id="heading-2-new-utility-function">2. New Utility Function</h3>
<p>A new async utility offloads data filtering:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> asyncFilterDataWithColumnAndCustomFilters = <span class="hljs-keyword">async</span> (
  data,
  columns,
  columnFilters,
  customFilters,
  setData,
  setIsSearching,
) =&gt; {
  <span class="hljs-keyword">const</span> searchWorker = <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.Worker(searchWorkerScript);

  <span class="hljs-keyword">const</span> convertedData = data.map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> ({
    id: row.id,
    values: columns.map(<span class="hljs-function"><span class="hljs-params">col</span> =&gt;</span> col.selector?.(row) ?? row[col.id]),
    items: (row.items || []).map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> ({
      id: item.id,
      values: columns.map(<span class="hljs-function"><span class="hljs-params">col</span> =&gt;</span> col.selector?.(item) ?? item[col.id]),
    })),
  }));

  searchWorker.postMessage({ data, convertedData, columns, columnFilters, customFilters });

  searchWorker.onmessage = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    setData(e.data.result);
    setIsSearching(<span class="hljs-literal">false</span>);
    searchWorker.terminate();
  };

  searchWorker.onerror = <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Worker error"</span>, err.message);
    setIsSearching(<span class="hljs-literal">false</span>);
    searchWorker.terminate();
  };
};
</code></pre>
<h3 id="heading-3-hook-update">3. Hook Update</h3>
<p>We extended the existing table filter hook:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useFilterDataWithColumnAndCustomFilters = <span class="hljs-function">(<span class="hljs-params">{
  data,
  columns,
  columnFilters,
  customFilters,
  setIsSearching,
}</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [filteredData, setFilteredData] = useState({ data: [], diff: <span class="hljs-number">0</span> });

  useEffect(<span class="hljs-function">() =&gt;</span> {
    asyncFilterDataWithColumnAndCustomFilters(
      data,
      columns,
      columnFilters,
      customFilters,
      setIsSearching,
      setFilteredData
    );
  }, [data, columnFilters, customFilters]);

  <span class="hljs-keyword">return</span> { filteredData: filteredData.data, diff: filteredData.diff };
};
</code></pre>
<h3 id="heading-4-loading-indicator">4. Loading Indicator</h3>
<p>We updated the table’s loading prop:</p>
<pre><code class="lang-tsx">&lt;NewTable
  isLoading={isLoading || isSearching}
  // ...
/&gt;
</code></pre>
<h2 id="heading-bonus-what-the-worker-can-handle">Bonus: What the Worker Can Handle</h2>
<ul>
<li><p><code>BOOLEAN</code>, <code>DATE</code>, <code>DATETIME</code>, and <code>NUMBER</code> types</p>
</li>
<li><p>Interval filters (e.g. date ranges)</p>
</li>
<li><p>Null checks: <code>eq: "null"</code> and <code>ne: "null"</code></p>
</li>
<li><p>Multi-select picklists</p>
</li>
<li><p>Filters nested rows <strong>and</strong> their parents</p>
</li>
</ul>
<h2 id="heading-benefits">Benefits</h2>
<ul>
<li><p><strong>UI never freezes</strong> during filtering</p>
</li>
<li><p><strong>Filters run faster</strong>, even on large datasets</p>
</li>
<li><p><strong>Code is modular</strong> and easy to maintain</p>
</li>
<li><p><strong>Great user experience</strong> with real-time filtering feedback</p>
</li>
</ul>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><p>Use <strong>Web Workers</strong> to offload expensive tasks in the browser.</p>
</li>
<li><p>Normalize complex data structures before sending to the worker.</p>
</li>
<li><p>Gracefully handle worker errors and show meaningful UI states.</p>
</li>
<li><p>It's surprisingly easy to integrate with React.</p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Offloading filtering to a Web Worker has been one of the most impactful performance wins for our frontend in recent times. If you're working with large tables or slow filters, <strong>give workers a shot</strong> — your users (and frame rate) will thank you.</p>
<p><strong>Want help integrating something similar into your React app?</strong><br />Feel free to <a target="_blank" href="https://ravgeet.in/contact">connect with me</a>.</p>
<p><em>I wrote this blog post for my company,</em> <a target="_blank" href="https://cloudanswers.com/"><em>CloudAnswers</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[How I Use GitHub Copilot and ChatGPT Together as a Frontend Developer]]></title><description><![CDATA[As a frontend developer, I'm constantly juggling between writing clean code, shipping features fast, and keeping my sanity intact. AI tools like GitHub Copilot and ChatGPT have completely changed how I work. While each tool is powerful on its own, us...]]></description><link>https://hashnode.ravgeet.in/how-i-use-github-copilot-and-chatgpt-together-as-a-frontend-developer</link><guid isPermaLink="true">https://hashnode.ravgeet.in/how-i-use-github-copilot-and-chatgpt-together-as-a-frontend-developer</guid><category><![CDATA[webdev]]></category><category><![CDATA[github copilot]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[React]]></category><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Ravgeet Dhillon]]></dc:creator><pubDate>Mon, 16 Jun 2025 08:09:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/kjqTlMHLci4/upload/60cb77f5390dac04a3a7d5c20b8ec41d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a frontend developer, I'm constantly juggling between writing clean code, shipping features fast, and keeping my sanity intact. AI tools like <strong>GitHub Copilot</strong> and <strong>ChatGPT</strong> have completely changed how I work. While each tool is powerful on its own, using them <strong>together</strong> has helped me work faster, think clearly, and code smartly.</p>
<p>Here’s how I personally use <strong>Copilot + ChatGPT</strong> in my daily workflow.</p>
<hr />
<h2 id="heading-tldr-my-quick-comparison">TL;DR: My Quick Comparison</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Tool</td><td>What I Use It For</td></tr>
</thead>
<tbody>
<tr>
<td><strong>GitHub Copilot</strong></td><td>Real-time code completion inside my IDE (VS Code)</td></tr>
<tr>
<td><strong>ChatGPT</strong></td><td>Explaining bugs, brainstorming UI, writing docs, or generating code blocks</td></tr>
</tbody>
</table>
</div><p>I see Copilot as my <strong>coding sidekick inside the editor</strong>, and ChatGPT as my <strong>thinking partner outside of it</strong>.</p>
<hr />
<h2 id="heading-real-workflow-building-a-react-component">Real Workflow: Building a React Component</h2>
<h3 id="heading-step-1-i-ask-chatgpt-to-help-plan-it">Step 1: I Ask ChatGPT to Help Plan It</h3>
<p>When I need something like a responsive navbar with Tailwind CSS, I type:</p>
<blockquote>
<p>"Create a responsive navbar with logo, links, and hamburger menu using React and Tailwind."</p>
</blockquote>
<p>ChatGPT usually gives me a great starting point with code, accessibility notes, and even file structure suggestions.</p>
<h3 id="heading-step-2-i-paste-that-into-vs-code-and-let-copilot-take-over">Step 2: I Paste That Into VS Code and Let Copilot Take Over</h3>
<p>As I begin typing:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Navbar</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
</code></pre>
<p>Copilot fills in:</p>
<pre><code class="lang-js">&lt;nav className=<span class="hljs-string">"bg-white shadow-md p-4 flex justify-between items-center"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl font-bold"</span>&gt;</span>Logo<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  ...
&lt;/nav&gt;
</code></pre>
<p>It handles the obvious stuff—repetitive patterns, responsive classes, even conditional rendering—while I focus on logic.</p>
<h2 id="heading-how-i-use-them-together-every-day">How I Use Them Together Every Day</h2>
<h3 id="heading-1-rapid-prototyping">1. <strong>Rapid Prototyping</strong></h3>
<ul>
<li><p>ChatGPT helps me quickly draft UI layouts.</p>
</li>
<li><p>Copilot finishes JSX, props, and common logic on the fly.</p>
</li>
</ul>
<h3 id="heading-2-debugging">2. <strong>Debugging</strong></h3>
<ul>
<li><p>When I hit an error, I paste it into ChatGPT and ask:</p>
<blockquote>
<p>"What’s wrong with this error?"</p>
</blockquote>
</li>
<li><p>Then I fix the bug in VS Code with Copilot suggesting inline changes.</p>
</li>
</ul>
<h3 id="heading-3-writing-tests">3. <strong>Writing Tests</strong></h3>
<ul>
<li><p>I ask ChatGPT to generate tests for my React components.</p>
</li>
<li><p>I then use Copilot to autocomplete the repetitive test setup or assertions.</p>
</li>
</ul>
<h3 id="heading-4-refactoring">4. <strong>Refactoring</strong></h3>
<ul>
<li><p>I send messy functions to ChatGPT to break them down and improve naming.</p>
</li>
<li><p>Back in VS Code, Copilot speeds up implementing the improved version.</p>
</li>
</ul>
<h3 id="heading-5-explaining-my-code">5. <strong>Explaining My Code</strong></h3>
<ul>
<li><p>When I want to document my code or explain it to my team, I ask ChatGPT:</p>
<blockquote>
<p>"Explain this hook and why the useEffect dependency array matters."</p>
</blockquote>
</li>
</ul>
<h2 id="heading-my-favorite-productivity-tips">My Favorite Productivity Tips</h2>
<h3 id="heading-tip-1-comment-driven-prompts-for-copilot">Tip 1: Comment-Driven Prompts for Copilot</h3>
<pre><code class="lang-js"><span class="hljs-comment">// Create a responsive card component with image and title</span>
</code></pre>
<p>Copilot often gives me a complete JSX block instantly.</p>
<h3 id="heading-tip-2-chatgpt-for-documentation">Tip 2: ChatGPT for Documentation</h3>
<p>I paste a tricky function into ChatGPT and say:</p>
<blockquote>
<p>"Write a JSDoc for this and suggest better variable names."</p>
</blockquote>
<h3 id="heading-tip-3-code-reviews-get-easier">Tip 3: Code Reviews Get Easier</h3>
<p>I now use GitHub Copilot to explain my PRs or generate commit messages. Copilot helps me fix minor issues before I even push.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750060967446/43b291bb-9d50-484b-9c46-cfc346eb6f86.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-bonus-tools-i-like">Bonus Tools I Like</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Tool</td><td>Why I Use It</td></tr>
</thead>
<tbody>
<tr>
<td><strong>VS Code + Copilot</strong></td><td>For live code suggestions</td></tr>
<tr>
<td><strong>ChatGPT Web + API</strong></td><td>For deeper analysis, ideas, and code generation</td></tr>
<tr>
<td><strong>ChatGPT Plugins</strong></td><td>For testing or GitHub integrations</td></tr>
</tbody>
</table>
</div><h2 id="heading-my-final-thoughts">My Final Thoughts</h2>
<p>Using both GitHub Copilot and ChatGPT hasn’t just saved me time—it’s <strong>leveled up how I think and build</strong>.</p>
<p>Copilot gives me instant coding speed in the editor, while ChatGPT helps me think clearly, plan ahead, and document better. Whether I’m building a component, squashing bugs, or writing docs, these two tools make the process smoother.</p>
<p>So if you're a frontend dev like me, give this duo a try. You might never want to code alone again. ✨</p>
]]></content:encoded></item></channel></rss>