<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ravgeet Dhillon</title>
    <description>The latest articles on Forem by Ravgeet Dhillon (@ravgeetdhillon).</description>
    <link>https://forem.com/ravgeetdhillon</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F373558%2F8760af2d-dba7-4600-a6bb-06e30469c96e.jpg</url>
      <title>Forem: Ravgeet Dhillon</title>
      <link>https://forem.com/ravgeetdhillon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ravgeetdhillon"/>
    <language>en</language>
    <item>
      <title>My First Electronics Project - Building a Real-Time Power Outage Monitor with ESP32 and Slack</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Wed, 18 Mar 2026 11:53:05 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/building-a-real-time-power-outage-monitor-with-esp32-and-slack-2kmk</link>
      <guid>https://forem.com/ravgeetdhillon/building-a-real-time-power-outage-monitor-with-esp32-and-slack-2kmk</guid>
      <description>&lt;p&gt;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."&lt;/p&gt;

&lt;p&gt;The internal monologue of a developer immediately goes to the worst-case scenario: &lt;em&gt;Did the router die? Is there a break-in? Did the server crash?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I promised myself I wouldn't leave for another trip without a "Heartbeat" from my home in Amritsar.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;I needed a non-invasive system (no cutting 220V mains wires), resilient to inverter switchover gaps, and capable of sending instant notifications.&lt;/p&gt;

&lt;p&gt;I chose the &lt;strong&gt;ESP32&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hardware Stack
&lt;/h3&gt;

&lt;p&gt;To keep things modular and "plug-and-play," I went with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ESP32 DevKit V1:&lt;/strong&gt; The brain of the operation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;USB-TTL Converter:&lt;/strong&gt; This acts as the sensor. It brings the 5V USB signal down to a safe 3.3V for the ESP32 to read.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;1000µF Capacitor:&lt;/strong&gt; Essential for bridging the 20ms gap during inverter switchover. This prevents the ESP32 from rebooting during the transition.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Female-to-Female jumper wires&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some USB data cables&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Engineering the "Flicker Filter"
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I solved this with a two-pronged approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grounding Geometry:&lt;/strong&gt; Moving the ground wires to the same side of the board to stabilize the reference voltage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Software Debounce:&lt;/strong&gt; I implemented a 3-second delay in the code. The system verifies that the power is &lt;em&gt;actually&lt;/em&gt; out before sending a Slack alert, preventing false alarms from minor grid fluctuations.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Watch the Journey
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Watch the Documentary:&lt;/strong&gt; &lt;a href="https://youtu.be/WmGZCAdeHMY" rel="noopener noreferrer"&gt;YouTube - Making my first Electronics project&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;

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


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Get the Code:&lt;/strong&gt; &lt;a href="https://github.com/ravgeetdhillon/esp32-power-alert" rel="noopener noreferrer"&gt;GitHub - esp32-power-alert&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Closing Thoughts
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're interested in building this yourself, feel free to reach out or check out the repository!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>hardware</category>
      <category>iot</category>
      <category>diy</category>
    </item>
    <item>
      <title>Debugging and Stopping Infinite Render Loops in React</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Thu, 05 Feb 2026 11:22:28 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/debugging-and-stopping-infinite-render-loops-in-react-fm9</link>
      <guid>https://forem.com/ravgeetdhillon/debugging-and-stopping-infinite-render-loops-in-react-fm9</guid>
      <description>&lt;p&gt;Infinite renders are not magic bugs — they are deterministic feedback loops. Once you understand &lt;em&gt;why&lt;/em&gt; a render retriggers itself, they become easy to reproduce, debug, and prevent.&lt;/p&gt;

&lt;p&gt;This post walks through a &lt;strong&gt;step‑by‑step mental model&lt;/strong&gt; to stop the “Maximum update depth reached“ errors for good.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Infinite Render Loop Really Is
&lt;/h2&gt;

&lt;p&gt;At its core, an infinite render loop looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Component renders&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some reactive logic runs (effect, watcher, computed, subscription)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That logic updates the state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;State update causes a re-render&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat forever&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Renders don’t loop by accident — they loop because state changes on every render.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Fastest Way to Reproduce the Bug
&lt;/h2&gt;

&lt;p&gt;When debugging, your first goal is &lt;strong&gt;reproduction&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal reproduction checklist
&lt;/h3&gt;

&lt;p&gt;Comment out everything except one state value and one reactive hook (effect/watcher). Then add a log in both render and state update.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;effect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;render → effect → render → effect → ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ve confirmed the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The #1 Root Cause: Unstable References
&lt;/h2&gt;

&lt;p&gt;Most infinite loops come from &lt;strong&gt;reference instability&lt;/strong&gt;, not logic errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity vs Equality
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if values &lt;em&gt;look&lt;/em&gt; equal, &lt;strong&gt;their identity changes&lt;/strong&gt; on every render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Object in dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ new object every render&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in the effect that runs forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stabilizing References with Memoization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fix with memoization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;userId&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it has the same reference, but the effect runs only when &lt;code&gt;userId&lt;/code&gt; changes. A rule of thumb is that if a variable goes into the dependency array, it must be referentially stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Correct Dependencies (Not Fewer)
&lt;/h2&gt;

&lt;p&gt;A common anti-pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This effect updates its own dependency once, then converges to a stable state instead of looping forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linting That Actually Helps
&lt;/h2&gt;

&lt;p&gt;Linting is one of the &lt;strong&gt;cheapest ways&lt;/strong&gt; to prevent infinite render loops &lt;em&gt;before&lt;/em&gt; they reach runtime — especially in React and Next.js apps.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;react-hooks/rules-of-hooks&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;react-hooks/exhaustive-deps&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This forces correct dependency lists, exposes unstable references early, and prevents stale closures disguised as "fixes".&lt;/p&gt;

&lt;p&gt;But still, the warnings about missing dependencies are not always true. It makes sense to treat them as &lt;strong&gt;design bugs&lt;/strong&gt;, not suggestions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging with &lt;code&gt;why-did-you-render&lt;/code&gt; + LLMs
&lt;/h2&gt;

&lt;p&gt;Although we are talking about this step at the very last, if time is crucial to you, then this could be the first resort.&lt;/p&gt;

&lt;p&gt;Sometimes you &lt;em&gt;know&lt;/em&gt; a component is re-rendering too much, but you don’t know &lt;strong&gt;what changed&lt;/strong&gt;. Multiple states or effects might be responsible for an infinite render loop.&lt;/p&gt;

&lt;p&gt;This is where &lt;code&gt;why-did-you-render&lt;/code&gt; becomes extremely powerful — especially when paired with an LLM.&lt;/p&gt;

&lt;h3&gt;
  
  
  What &lt;code&gt;why-did-you-render&lt;/code&gt; does
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;why-did-you-render&lt;/code&gt; monkey-patches React in development and logs &lt;strong&gt;exact reasons&lt;/strong&gt; for re-renders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which props changed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Whether the change was by &lt;strong&gt;identity&lt;/strong&gt; or &lt;strong&gt;value&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which hooks triggered the update&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of guessing, you get concrete evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The values are equal, but the &lt;strong&gt;reference is not&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Memoization is missing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feeding logs to an LLM (Copilot / ChatGPT)
&lt;/h3&gt;

&lt;p&gt;Here’s where debugging gets &lt;em&gt;much&lt;/em&gt; faster.&lt;/p&gt;

&lt;p&gt;You can save the console logs as a &lt;code&gt;.log&lt;/code&gt; 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:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the logs already encode &lt;strong&gt;identity vs equality&lt;/strong&gt;, the LLM can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Identify unstable objects/functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suggest correct &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt; placement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Detect unnecessary props drilling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recommend architectural fixes (lifting state, memo boundaries)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This removes the guesswork that usually slows humans down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this works so well with LLMs
&lt;/h3&gt;

&lt;p&gt;LLMs struggle with &lt;em&gt;implicit runtime behavior&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;why-did-you-render&lt;/code&gt; turns runtime behavior into &lt;strong&gt;explicit text&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once behavior is textual:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Debugging becomes a reasoning problem — which LLMs are good at.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Used together, they form a tight loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Reproduce the render issue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Capture &lt;code&gt;why-did-you-render&lt;/code&gt; logs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste logs + code into an LLM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apply fix&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify render stability&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Infinite render loops are not a framework flaw — they are a &lt;strong&gt;signal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They tell you that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Data flow is unstable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identity is misunderstood&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or side effects are misplaced&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you respect reference stability and dependency correctness, infinite loops disappear — permanently.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Rebuilding My Static Blog with Build-Time Data and Instant Search</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Wed, 04 Feb 2026 17:51:24 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/rebuilding-my-static-blog-with-build-time-data-and-instant-search-16db</link>
      <guid>https://forem.com/ravgeetdhillon/rebuilding-my-static-blog-with-build-time-data-and-instant-search-16db</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Last week, I rebuilt the blog section of &lt;strong&gt;ravgeet.in&lt;/strong&gt; (Nuxt.js) to fix this properly. The end result is still a static site, but now it feels &lt;em&gt;alive&lt;/em&gt;: aggregated view counts, instant search and sorting, and zero runtime dependencies on external APIs.&lt;/p&gt;

&lt;p&gt;This post walks through the thinking, architecture, and trade-offs behind that rebuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with my old setup
&lt;/h2&gt;

&lt;p&gt;Originally, my blog worked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Blog content lived on &lt;strong&gt;Hashnode&lt;/strong&gt; (canonical source)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some posts were also cross-posted to &lt;strong&gt;Dev.to&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pages fetched blog data &lt;strong&gt;at runtime&lt;/strong&gt; using Hashnode’s GraphQL API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pagination logic (&lt;code&gt;hasNextPage&lt;/code&gt;, cursors) lived inside the UI&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This had a few downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A static site depending on live APIs felt wrong&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Local development and builds were slower and flaky&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding features like search or sorting would require more APIs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted the blog to stay static - but smarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build-time data as a contract
&lt;/h2&gt;

&lt;p&gt;The core decision was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Move all external data fetching to build time, and treat the result as immutable static data.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of fetching blogs at runtime, I introduced a build step that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fetches blogs from Hashnode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fetches articles from Dev.to&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Matches the same article across platforms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Aggregates view counts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writes everything into a single JSON file&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At runtime, the site only reads from that JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hashnode + Dev.to
        ↓
Build-time fetch &amp;amp; normalize
        ↓
static/blogs.json
        ↓
Nuxt UI (search, sort, views)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one decision simplified everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching and aggregating blog data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hashnode: canonical content
&lt;/h3&gt;

&lt;p&gt;Hashnode remains the source of truth for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Title, slug, content, tags&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publish date&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cover image&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Base view count&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I fetch all posts using Hashnode’s GraphQL API with pagination handled inside a Node.js script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev.to: distribution and extra reach
&lt;/h3&gt;

&lt;p&gt;Dev.to is where additional readers come from, so ignoring those views felt wrong.&lt;/p&gt;

&lt;p&gt;Using the Dev.to API (with a personal access token), I fetch all my articles and extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;slug&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;canonical_url&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;page_views_count&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Matching articles across platforms
&lt;/h3&gt;

&lt;p&gt;This is the tricky part. Articles are matched using a layered strategy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slug match&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Canonical URL match&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Title match&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once matched, the final view count becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;combinedViews = hashnodeViews + devtoViews
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output for each blog includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Combined views&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Platform-specific views (for debugging)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dev.to URL (if matched)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing the static data contract
&lt;/h2&gt;

&lt;p&gt;All processed data is written to the &lt;code&gt;static/blogs.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;This file is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Generated at build time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git-ignored&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treated as read-only by the app&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also includes metadata like the last updated time and the total blog count.&lt;/p&gt;

&lt;p&gt;This JSON file effectively replaces my entire blog API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacing runtime APIs with static services
&lt;/h2&gt;

&lt;p&gt;Previously, &lt;code&gt;services/blogs.js&lt;/code&gt; made live GraphQL calls. After the refactor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The service dynamically imports &lt;code&gt;blogs.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;find&lt;/code&gt;, &lt;code&gt;findOne&lt;/code&gt;, and &lt;code&gt;search&lt;/code&gt; all operate locally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No Axios&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No pagination state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No network failures&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the UI’s perspective, nothing changed - but under the hood, everything became predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instant search and sorting
&lt;/h2&gt;

&lt;p&gt;Once all blog data is local, search becomes trivial.&lt;/p&gt;

&lt;p&gt;I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Client-side text search (title, brief, tags)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sorting by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Published date (recent / oldest)&lt;/li&gt;
&lt;li&gt;View count (most / least)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Because the dataset is small and static:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Search results are instant&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No debouncing hacks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No loading states&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sorting is deterministic&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This dramatically improves discoverability without introducing a search service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and lessons learned
&lt;/h2&gt;

&lt;p&gt;This approach isn’t perfect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Build time increases slightly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The JSON file grows over time&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It’s not suitable for real-time analytics&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for a personal blog, the trade-offs are worth it.&lt;/p&gt;

&lt;p&gt;The key takeaways from the refactor that made me realize that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Static doesn’t mean lifeless&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build-time data pipelines are underrated&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One clean data contract simplifies UI, UX, and performance&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re curious, the full implementation lives in the &lt;a href="https://github.com/ravgeetdhillon/ravgeet-web" rel="noopener noreferrer"&gt;ravgeet.in repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>website</category>
    </item>
    <item>
      <title>My 7 Aspirations as a Software Engineer in 2026</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Tue, 27 Jan 2026 09:30:14 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/my-7-aspirations-as-a-software-engineer-in-2026-2fdb</link>
      <guid>https://forem.com/ravgeetdhillon/my-7-aspirations-as-a-software-engineer-in-2026-2fdb</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This post is not a checklist of goals. Instead, it’s a reflection on the &lt;em&gt;direction&lt;/em&gt; I want my career to move in — as an engineer who values strong fundamentals, meaningful leverage, and sustainable, long-term growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Mastering the Fundamentals
&lt;/h2&gt;

&lt;p&gt;By 2026, my primary aspiration is to be &lt;strong&gt;fundamentally strong&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not just "good at React" or "familiar with backend systems" but genuinely comfortable explaining &lt;em&gt;why&lt;/em&gt; things behave the way they do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How JavaScript works under the hood&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How browsers render, schedule, and optimize work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How React actually reconciles, schedules, and re-renders&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How data flows through a system end-to-end&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to be the engineer who can debug issues calmly because I understand the system — not because I’ve memorized fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Thinking in Systems, Not Just Features
&lt;/h2&gt;

&lt;p&gt;Another aspiration is to shift from &lt;strong&gt;feature-level thinking&lt;/strong&gt; to &lt;strong&gt;system-level thinking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How do I implement this requirement?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I want my default question to be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How does this decision affect the system six months from now?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means caring about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Trade-offs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintainability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Operational complexity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developer experience&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good engineers ship features. Great engineers design systems that &lt;em&gt;survive change&lt;/em&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Becoming a Strong Communicator, Not Just a Coder
&lt;/h2&gt;

&lt;p&gt;By 2026, I want my value to extend beyond code.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Explaining complex ideas clearly to other engineers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing thoughtful PR descriptions and design docs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding working evidence in the PRs in the form of videos and screenshots&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Helping juniors build correct mental models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disagreeing respectfully and productively&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software engineering is a team sport. Clear thinking is useless if it can’t be communicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Building Leverage Through Tools and Automation
&lt;/h2&gt;

&lt;p&gt;One of my strongest aspirations is to &lt;strong&gt;build leverage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of solving the same problems repeatedly, I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Automate workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build internal tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create systems that scale my impact beyond my own output&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leverage is what separates engineers who work &lt;em&gt;hard&lt;/em&gt; from engineers who work &lt;em&gt;effectively&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Developing Taste and Judgment
&lt;/h2&gt;

&lt;p&gt;Technical skills can be learned. &lt;strong&gt;Judgment takes time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By 2026, I want to develop a strong engineering taste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Knowing when &lt;em&gt;not&lt;/em&gt; to over-engineer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Knowing when technical debt is acceptable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choosing boring solutions when they’re the right ones&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This kind of judgment only comes from reflection, mistakes, and intentional learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Staying Curious Without Burning Out
&lt;/h2&gt;

&lt;p&gt;Finally, I aspire to stay curious — but sustainable.&lt;/p&gt;

&lt;p&gt;Not chasing every new framework, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Learning deeply&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Picking tools intentionally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Balancing ambition with health&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Longevity matters. I want a career that compounds, not one that exhausts me early.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Launching at Least One Production-grade App
&lt;/h2&gt;

&lt;p&gt;By the end of 2026, I want to have launched &lt;strong&gt;at least one app that real users can use&lt;/strong&gt; — not just a side project living on GitHub.&lt;/p&gt;

&lt;p&gt;This aspiration is less about building a startup and more about &lt;strong&gt;closing the loop&lt;/strong&gt; as an engineer. From idea to execution to feedback, I want to experience the full lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Identifying a real problem (even a small one)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Designing a simple, opinionated solution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Making trade-offs under real constraints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping, maintaining, and iterating based on usage&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;More importantly, it sharpens judgment. You quickly learn what &lt;em&gt;actually&lt;/em&gt; matters to users, which abstractions are worth the cost, and which technical decisions age poorly once real usage begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;My aspirations for 2026 are less about &lt;em&gt;where&lt;/em&gt; I work and more about &lt;em&gt;how&lt;/em&gt; I work and think.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I plan to revisit this post at the end of the year to assess how closely my reality aligned with these intentions, honestly.&lt;/p&gt;

</description>
      <category>growth</category>
      <category>softwaredevelopment</category>
      <category>personaldevelopment</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Realtime Deploy Notifications in Next.js with Toasts</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Sun, 25 Jan 2026 05:30:48 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/realtime-deploy-notifications-in-nextjs-with-toasts-17lm</link>
      <guid>https://forem.com/ravgeetdhillon/realtime-deploy-notifications-in-nextjs-with-toasts-17lm</guid>
      <description>&lt;p&gt;Ever deployed a new version of your app and wished your users got notified &lt;strong&gt;instantly&lt;/strong&gt; while they’re still using the old one? In this post, I'll show you how I built a &lt;strong&gt;live build checker&lt;/strong&gt; in my Next.js app that detects when a new deployment is live and gently nudges users to refresh using a toast notification.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use case: Helpful for apps deployed on Vercel where static pages and edge functions make it easy to serve outdated content across sessions.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Next.js 15&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React 19&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React-Bootstrap&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React-Toastify&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vercel Edge Functions&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supabase Auth (optional)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Concept
&lt;/h2&gt;

&lt;p&gt;When a new build is deployed, a unique timestamp (&lt;code&gt;NEXT_PUBLIC_BUILD_TIMESTAMP&lt;/code&gt;) is injected into the environment. On the client, we &lt;strong&gt;poll an API endpoint&lt;/strong&gt; every few minutes to check if the server's build timestamp has changed. If it has, we notify the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Add a build timestamp during build time
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;next.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;NEXT_PUBLIC_BUILD_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;nextConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every build has a unique timestamp baked in at compile time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a &lt;code&gt;/api/build&lt;/code&gt; route
&lt;/h3&gt;

&lt;p&gt;This edge function returns the current server build timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BUILD_TIMESTAMP&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also add authentication here if needed, e.g., for private dashboards.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Build a custom hook: &lt;code&gt;useLiveBuildChecker&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This hook polls the &lt;code&gt;/api/build&lt;/code&gt; endpoint periodically and triggers a toast if it detects a new version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-toastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useLiveBuildChecker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalMin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentBuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_BUILD_TIMESTAMP&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkForUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;currentBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A new version of this page is available. Refresh to see the latest changes.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;autoClose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottom-right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// stop further polling&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Update check failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;checkForUpdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;intervalMin&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;intervalMin&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Add Toast UI + Hook in Layout
&lt;/h3&gt;

&lt;p&gt;Update your app layout to include the &lt;code&gt;ToastContainer&lt;/code&gt; and run the hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ToastContainer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-toastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLiveBuildChecker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/hooks/useLiveBuildChecker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-toastify/dist/ReactToastify.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AppLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useLiveBuildChecker&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// default 5-min interval&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ToastContainer&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Whenever a new version of your app is deployed, users still active in the browser will get this neat toast:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"&lt;/strong&gt;A new version of this page is available. Refresh to see the latest changes.&lt;strong&gt;"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowg1s5p6uzarwttg506s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowg1s5p6uzarwttg506s.png" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;This approach is fully &lt;strong&gt;edge-friendly&lt;/strong&gt;, &lt;strong&gt;stateless&lt;/strong&gt;, and &lt;strong&gt;easy to scale&lt;/strong&gt;. And unlike service worker-based update detection, it works great for &lt;strong&gt;SSR/ISR setups&lt;/strong&gt; and &lt;strong&gt;custom dashboards&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus Ideas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can trigger a background update via service workers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can track how long users stay on an outdated version.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If you're building a dashboard, personal tool, or even a SaaS product, this is one of those &lt;em&gt;delightfully simple yet powerful&lt;/em&gt; improvements.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>saas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How I Built a Unified Calendar Dashboard with Next.js, Vercel Edge Functions &amp; No Database</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Wed, 05 Nov 2025 04:43:54 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-no-database-368k</link>
      <guid>https://forem.com/ravgeetdhillon/how-i-built-a-unified-calendar-dashboard-with-nextjs-vercel-edge-functions-no-database-368k</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I was juggling tasks across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Company ClickUp (for team collaboration)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Notion (for personal to-dos and planning)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Calendar (from both company &amp;amp; personal emails)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;I built a &lt;strong&gt;read-only personal dashboard&lt;/strong&gt; that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Aggregates tasks/events from ClickUp, Notion, and Google Calendar&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Groups tasks as &lt;strong&gt;Overdue&lt;/strong&gt;, &lt;strong&gt;Due Today&lt;/strong&gt;, &lt;strong&gt;Upcoming by Date,&lt;/strong&gt; and &lt;strong&gt;No Due Date&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runs entirely on &lt;strong&gt;Next.js + Edge Functions&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;strong&gt;no database&lt;/strong&gt;, just live API reads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stores my daily tasks that I need to do&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is &lt;strong&gt;password protected&lt;/strong&gt; and deployed on a Vercel subdomain&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how it looks after multiple polishes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohxy2th1q6qqlxm5872p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohxy2th1q6qqlxm5872p.png" alt=" " width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js App Router + React Bootstrap + TanStack Query&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API Layer&lt;/strong&gt;: Vercel Edge Functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auth&lt;/strong&gt;: Cookie-based with middleware protection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hosting&lt;/strong&gt;: Vercel subdomain&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unified Task View
&lt;/h3&gt;

&lt;p&gt;Each task is grouped into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Overdue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Due Today / Tomorrow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upcoming&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No Due Date&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It pulls data from these 3 APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;clickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchClickupTasks&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchNotionTasks&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchCalendarEvents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auth with Middleware
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/logout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicPaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/clickup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/notion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/calendar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Modular API Fetchers
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/sources/clickup.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchClickupTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.clickup.com/api/v2/...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same goes for Notion &amp;amp; Google Calendar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server API Route Example
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/events/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAllEvents&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/getAllEvents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calendar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllEvents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;clickup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;calendar&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Frontend UI
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Using TanStack Query for live fetching and caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/events&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we categorize tasks by due date:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overdue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dueToday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isToday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upcoming&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupByDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(...));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noDueDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dueDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7kggtem5iq5adb4zxf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7kggtem5iq5adb4zxf3.png" alt=" " width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtzlmozz41s1qe4db4bf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtzlmozz41s1qe4db4bf.png" alt=" " width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I deployed the app to Vercel and created a subdomain via Hostinger by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Creating a subdomain DNS record&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding the domain to Vercel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting env variables and password via the Vercel dashboard&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No secrets or tasks are stored — it's 100% live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;I'm happy with the current version, but I could add the following features in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Month View toggle&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Desktop notifications for overdue tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auto-refresh every 10 mins&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tauri or Expo wrapper for mobile&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If you're tired of hopping between 5 apps, build something simple that fits your brain.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Smart Hardware Inventory System That Actually Works</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Mon, 13 Oct 2025 18:14:12 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/building-a-smart-hardware-inventory-system-that-actually-works-ngg</link>
      <guid>https://forem.com/ravgeetdhillon/building-a-smart-hardware-inventory-system-that-actually-works-ngg</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here's how I built a hardware inventory system that's simple enough to maintain and smart enough to find anything instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Hardware Chaos
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Every time I needed something specific, I'd end up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Plugging in random drives to check their contents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing cables to see what they actually do&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wasting 10-15 minutes on something that should take 30 seconds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There had to be a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Smart Simplicity
&lt;/h2&gt;

&lt;p&gt;Instead of over-engineering this, I decided to build something that would work with tools I already use daily. My requirements were simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quick to update&lt;/strong&gt; - Adding new items shouldn't be a chore&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accessible anywhere&lt;/strong&gt; - No app installations or complex logins&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Physical integration&lt;/strong&gt; - Must work with actual hardware, not just digital records&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intelligent search&lt;/strong&gt; - Find items by description, not just exact matches&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Building the System: A Step-by-Step Breakdown
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: The Foundation - Google Sheets as a Database
&lt;/h3&gt;

&lt;p&gt;I started with a clean Google Sheet with these columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ID&lt;/strong&gt;: Auto-generated unique identifier&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type&lt;/strong&gt;: Category like "USB Drive", "Cable", "Charger"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;: The magic field where I describe each item in plain English&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Date Added&lt;/strong&gt;: Automatic timestamp&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt;: Available, In Use, Lost&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing revolutionary here, but the key was keeping it simple enough that I'd actually maintain it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Smart ID Generation with Apps Script
&lt;/h3&gt;

&lt;p&gt;Manual ID assignment? No thanks. I wrote a Google Apps Script function that generates short, unique IDs automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateUniqueID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSheet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingIDs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A:A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;newID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingIDs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newID&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates memorable 3-character IDs like &lt;code&gt;XR7&lt;/code&gt; or &lt;code&gt;K2M&lt;/code&gt;. Short enough to write on tiny labels, unique enough to avoid conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Bridging Digital and Physical Worlds
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The system generates a unique ID&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I write that ID on a physical label&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I stick the label directly on the hardware&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip&lt;/strong&gt;: For items too small for labels (like tiny dongles), I use small zip-lock bags with labeled sticky notes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Web Access Without the Hassle
&lt;/h3&gt;

&lt;p&gt;Opening Google Sheets every time felt clunky. Instead, I:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Published the sheet as a web app through Apps Script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up a custom subdomain using Vercel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Created a clean, mobile-friendly interface&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now I can check my inventory from my phone while standing in front of my hardware drawer. Game-changer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: AI-Powered Search That Actually Understands
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Find the Kingston drive with project files"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Which cable charges my laptop?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Show me USB drives over 16GB"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Impact: Why This Actually Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;: "I need that specific USB drive... &lt;em&gt;proceeds to test 6 different drives&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;: "I need that specific USB drive" → Check AI search → "It's the one labeled K2M" → Done in 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gic5ixdrcvqiy5tq73e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gic5ixdrcvqiy5tq73e.png" alt=" " width="800" height="904"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;p&gt;After 3 months of use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;45 items&lt;/strong&gt; tracked (drives, cables, adapters, dongles)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Average search time&lt;/strong&gt;: 15 seconds (down from 10+ minutes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time to add new item&lt;/strong&gt;: 90 seconds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Works Really Well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Physical labels are non-negotiable&lt;/strong&gt; - Digital-only systems fail in the real world&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI search is a multiplier&lt;/strong&gt; - Turns a simple spreadsheet into something genuinely intelligent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity scales&lt;/strong&gt; - Started with pendrives, now tracks everything&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next: Future Improvements
&lt;/h2&gt;

&lt;p&gt;The foundation is solid, so I'm adding features that solve real pain points:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QR Code Integration&lt;/strong&gt;: Generate QR codes for each item that link directly to their details. Scan with phone → instant access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capacity Analytics&lt;/strong&gt;: Total up storage capacity across all drives, and identify gaps in my hardware collection.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>openai</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Basic AI Agent</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Sat, 11 Oct 2025 13:35:15 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/building-an-ai-powered-function-orchestrator-when-ai-becomes-your-code-planner-5d6o</link>
      <guid>https://forem.com/ravgeetdhillon/building-an-ai-powered-function-orchestrator-when-ai-becomes-your-code-planner-5d6o</guid>
      <description>&lt;p&gt;As developers, we constantly face a dilemma: &lt;strong&gt;how do we make our code flexible enough to handle natural human requests without hardcoding every possible scenario?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While working on various automation projects, I kept running into the same pattern—users would ask for something in plain English, like &lt;em&gt;"Can you calculate 2+25-4^2?&lt;/em&gt; or &lt;em&gt;"Process these files and send me a summary",&lt;/em&gt; but my code was rigid, expecting specific formats and predefined workflows.&lt;/p&gt;

&lt;p&gt;The breakthrough came when I realized: &lt;strong&gt;What if AI handled the planning, and I just focused on building solid, reusable functions?&lt;/strong&gt; Instead of trying to anticipate every user request, let AI interpret the intent and dynamically orchestrate my functions.&lt;/p&gt;

&lt;p&gt;This post walks through building a mini AI agent framework that separates &lt;strong&gt;what you can do&lt;/strong&gt; (functions) from &lt;strong&gt;how to do it&lt;/strong&gt; (AI planning). The result? Code that adapts to human intent rather than forcing humans to adapt to your interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Traditional Code vs. Human Intent
&lt;/h2&gt;

&lt;p&gt;How many times have you written code that looks like this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complex_math_solver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse expression
&lt;/span&gt;    &lt;span class="c1"&gt;# Apply PEMDAS rules
&lt;/span&gt;    &lt;span class="c1"&gt;# Handle edge cases
&lt;/span&gt;    &lt;span class="c1"&gt;# Return result
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem? You're cramming &lt;strong&gt;parsing logic&lt;/strong&gt;, &lt;strong&gt;mathematical rules&lt;/strong&gt;, and &lt;strong&gt;execution&lt;/strong&gt; into one monolithic function. What if we could separate these concerns entirely?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: AI as a Function Orchestrator
&lt;/h2&gt;

&lt;p&gt;Instead of hardcoding business logic, what if we let AI handle the &lt;strong&gt;planning&lt;/strong&gt; while we focus on building &lt;strong&gt;atomic, reusable functions&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Here's the architecture:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Atomic Functions
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="c1"&gt;# Function registry for dynamic execution
&lt;/span&gt;&lt;span class="n"&gt;FUNCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multiply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;power&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Document Your Functions (For AI)
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DOCS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Add two numbers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multiply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Multiply two numbers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;power&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raise a to the power of b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a float or int number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Let AI Generate Execution Plans
&lt;/h3&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_action_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    You are an AI planner. Convert the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s request into a step-by-step plan.

    Available functions:
    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

    User&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s query: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

    Return a JSON plan with ordered steps.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real Example: "Solve 2+2*5+4^2"
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input:&lt;/strong&gt; Natural language request&lt;br&gt;&lt;br&gt;
&lt;strong&gt;AI Output:&lt;/strong&gt; Structured execution plan&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"power"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"multiply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;result_of_step_2&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sum"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;result_of_step_3&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;result_of_step_1&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Execution Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Magic: Dynamic Plan Execution
&lt;/h2&gt;

&lt;p&gt;This function takes the AI-generated plan and executes it step by step. The key insight is &lt;strong&gt;result chaining&lt;/strong&gt;—each step can reference outputs from previous steps using placeholders like &lt;code&gt;&amp;lt;result_of_step_1&amp;gt;&lt;/code&gt;. The system automatically resolves these references, creating a dynamic pipeline where complex calculations emerge from simple function compositions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;func_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Replace placeholders with previous results
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;result_of_step_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;step_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;step_idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Execute function dynamically
&lt;/span&gt;        &lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FUNCTIONS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Step &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) → &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Architecture Wins
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Separation of Concerns&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You write:&lt;/strong&gt; Pure, testable functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI handles:&lt;/strong&gt; Complex planning and orchestration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System manages:&lt;/strong&gt; Execution flow and state&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Human-in-the-Loop Safety&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;❌ Error in plan generation:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Safe exit on planning failures
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if a user asks &lt;em&gt;"Divide 10 by 0 and add 5"&lt;/em&gt;, the AI can detect this is mathematically impossible and return an error response like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cannot divide by zero - this operation is undefined in mathematics"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents dangerous operations from executing and provides clear feedback to users.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Infinite Extensibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Today, it's math functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FUNCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multiply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;divide&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;divide&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tomorrow it could be &lt;strong&gt;anything&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FUNCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query_database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fetch_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;weather_api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analyze_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  File Operations
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Take all .txt files in /docs, extract headings, and create a summary document"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Functions:&lt;/strong&gt; &lt;code&gt;list_files()&lt;/code&gt;, &lt;code&gt;read_file()&lt;/code&gt;, &lt;code&gt;extract_headings()&lt;/code&gt;, &lt;code&gt;create_document()&lt;/code&gt;, &lt;code&gt;write_file()&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  API Orchestration
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Get weather for New York, if it's raining, send a Slack message to #general"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Functions:&lt;/strong&gt; &lt;code&gt;fetch_weather()&lt;/code&gt;, &lt;code&gt;check_condition()&lt;/code&gt;, &lt;code&gt;send_slack_message()&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Load sales.csv, calculate monthly averages, generate a chart, and email it to the team"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Functions:&lt;/strong&gt; &lt;code&gt;load_csv()&lt;/code&gt;, &lt;code&gt;calculate_average()&lt;/code&gt;, &lt;code&gt;group_by_month()&lt;/code&gt;, &lt;code&gt;generate_chart()&lt;/code&gt;, &lt;code&gt;send_email()&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Server Integration
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;"Query our customer database, analyze sentiment of recent feedback, and create a dashboard"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Required Functions:&lt;/strong&gt; &lt;code&gt;query_database()&lt;/code&gt;, &lt;code&gt;analyze_sentiment()&lt;/code&gt;, &lt;code&gt;aggregate_data()&lt;/code&gt;, &lt;code&gt;create_dashboard()&lt;/code&gt;, &lt;code&gt;save_report()&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;This isn't just a math solver — it's a &lt;strong&gt;mini AI agent framework&lt;/strong&gt;. The system provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Function Library:&lt;/strong&gt; Atomic, reusable components&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI Planner:&lt;/strong&gt; Intelligent request interpretation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execution Engine:&lt;/strong&gt; Safe, traceable function orchestration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Human Oversight:&lt;/strong&gt; Approval and error handling&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add more function types&lt;/strong&gt; (file ops, API calls)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement approval workflows&lt;/strong&gt; (show plan before execution)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add function validation&lt;/strong&gt; (type checking, parameter validation)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a web interface&lt;/strong&gt; (make it accessible to non-developers)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrate with MCP servers&lt;/strong&gt; (extend to complex business logic)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The bottom line:&lt;/strong&gt; 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.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agenticai</category>
      <category>openai</category>
      <category>programmingblogs</category>
    </item>
    <item>
      <title>The ultimate guide to Python logging</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Wed, 27 Aug 2025 04:30:31 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/the-ultimate-guide-to-python-logging-kdc</link>
      <guid>https://forem.com/ravgeetdhillon/the-ultimate-guide-to-python-logging-kdc</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;logging&lt;/strong&gt; is useful. Without logging, it's impossible to have insight or observability into what your application is actually doing.&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;Read the full blog on &lt;a href="https://www.honeybadger.io/blog/python-logging/" rel="noopener noreferrer"&gt;Honeybadger&lt;/a&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.honeybadger.io/blog/python-logging/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fondemand.bannerbear.com%2Fsignedurl%2FnZ52rq9EkQ6V3bp1Lj%2Fimage.jpg%3Fmodifications%3DW3sibmFtZSI6InRpdGxlIiwidGV4dCI6IlRoZSB1bHRpbWF0ZSBndWlkZSB0byBQeXRob24gbG9nZ2luZyIsImNvbG9yIjpudWxsLCJiYWNrZ3JvdW5kIjpudWxsfSx7Im5hbWUiOiJoZWFkc2hvdCIsImltYWdlX3VybCI6Imh0dHBzOi8vd3d3LWZpbGVzLmhvbmV5YmFkZ2VyLmlvL2F1dGhvcnMvcmF2Z2VldGRoaWxsb24ucG5nIn0seyJuYW1lIjoic3VtbWFyeSIsInRleHQiOiJMb2dnaW5nIGlzIGEgdmFsdWFibGUgdG9vbCBmb3IgcGVyZm9ybWFuY2UsIG1vbml0b3JpbmcsIGFuZCBkZWJ1Z2dpbmcuIEp1bXAgaW50byB0aGlzIGFydGljbGUgdG8gbGVhcm4gYmVzdCBwcmFjdGljZXMgZm9yIHByYWN0aWNhbCBQeXRob24gbG9nZ2luZy4iLCJjb2xvciI6bnVsbCwiYmFja2dyb3VuZCI6bnVsbH0seyJuYW1lIjoiYXV0aG9yIiwidGV4dCI6IkJ5ICpSYXZnZWV0IERoaWxsb24qIiwiY29sb3IiOm51bGwsImJhY2tncm91bmQiOm51bGx9LHsibmFtZSI6InRhZ3MiLCJ0ZXh0IjoiI3B5dGhvbiAjbG9nZ2luZyAjZGphbmdvIiwiY29sb3IiOm51bGwsImJhY2tncm91bmQiOm51bGx9XQ%26s%3Da9f8d916cc9912a0c36701d8ad5f90c92cacbd9f445b9642e2887478dc018127" height="628" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.honeybadger.io/blog/python-logging/" rel="noopener noreferrer" class="c-link"&gt;
            The ultimate guide to Python logging - Honeybadger Developer Blog
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Logging is a valuable tool for performance, monitoring, and debugging. Jump into this article to learn best practices for practical Python logging.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.honeybadger.io%2Ffavicon-32x32.png%3F1756258545" width="32" height="32"&gt;
          honeybadger.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Thanks for reading 💜&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;I publish a monthly newsletter in which&lt;/a&gt; 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&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;ross while surfing the web.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;Connect with&lt;/a&gt; &lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;me through Twitter • LinkedIn • Github or&lt;/a&gt; send me an &lt;a href="//mailto:ravgeetdhillon@gmail.com"&gt;Email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://www.ravgeet.in/" rel="noopener noreferrer"&gt;Ravgeet&lt;/a&gt;, &lt;em&gt;Full Stack Developer and Technical Content Writer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>logs</category>
      <category>programmingblogs</category>
      <category>generalprogramming</category>
    </item>
    <item>
      <title>Working with Markdown in Python</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Wed, 27 Aug 2025 04:28:57 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/working-with-markdown-in-python-2k5</link>
      <guid>https://forem.com/ravgeetdhillon/working-with-markdown-in-python-2k5</guid>
      <description>&lt;p&gt;If you use the Internet, you have surely come across the term &lt;strong&gt;Markdown.&lt;/strong&gt; &lt;a href="https://daringfireball.net/projects/markdown/" rel="noopener noreferrer"&gt;Markdown&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;Markdown is one of the most widely used formats for storing formatted &lt;a href="https://daringfireball.net/projects/markdown/" rel="noopener noreferrer"&gt;data. It&lt;/a&gt; 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.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to work with Markdown in a Python ap&lt;a href="https://daringfireball.net/projects/markdown/" rel="noopener noreferrer"&gt;plicatio&lt;/a&gt;n using different Python packages, including markdown, front matter, and markdownify.&lt;/p&gt;

&lt;p&gt;Read the full blog on &lt;a href="https://www.honeybadger.io/blog/python-markdown/" rel="noopener noreferrer"&gt;Honeybadger&lt;/a&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.honeybadger.io/blog/python-markdown/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fondemand.bannerbear.com%2Fsignedurl%2FnZ52rq9EkQ6V3bp1Lj%2Fimage.jpg%3Fmodifications%3DW3sibmFtZSI6InRpdGxlIiwidGV4dCI6Ildvcmtpbmcgd2l0aCBNYXJrZG93biBpbiBQeXRob24iLCJjb2xvciI6bnVsbCwiYmFja2dyb3VuZCI6bnVsbH0seyJuYW1lIjoiaGVhZHNob3QiLCJpbWFnZV91cmwiOiJodHRwczovL3d3dy1maWxlcy5ob25leWJhZGdlci5pby9hdXRob3JzL3JhdmdlZXRkaGlsbG9uLnBuZyJ9LHsibmFtZSI6InN1bW1hcnkiLCJ0ZXh0IjoiTWFya2Rvd24gbWFrZXMgaXQgZWFzeSB0byBhZGQgc3ludGF4IHRvIHlvdXIgcGxhaW4gdGV4dCBkb2N1bWVudHMgZm9yIHJlYWRhYmlsaXR5IGFuZCBtYWNoaW5lIHBhcnNpbmcuIEluIHRoaXMgYXJ0aWNsZSwgbGVhcm4gaG93IHRvIHdvcmsgd2l0aCBtYXJrZG93biBpbiBQeXRob24gdXNpbmcgdGhlIF9weXRob24tbWFya2Rvd25fIHBhY2thZ2UuIiwiY29sb3IiOm51bGwsImJhY2tncm91bmQiOm51bGx9LHsibmFtZSI6ImF1dGhvciIsInRleHQiOiJCeSAqUmF2Z2VldCBEaGlsbG9uKiIsImNvbG9yIjpudWxsLCJiYWNrZ3JvdW5kIjpudWxsfSx7Im5hbWUiOiJ0YWdzIiwidGV4dCI6IiNweXRob24gI21hcmtkb3duIiwiY29sb3IiOm51bGwsImJhY2tncm91bmQiOm51bGx9XQ%26s%3Dab5b1d543b2ad62ef569d035788be6864467222872a2263e3ba562c9c4431704" height="628" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.honeybadger.io/blog/python-markdown/" rel="noopener noreferrer" class="c-link"&gt;
            Working with Markdown in Python - Honeybadger Developer Blog
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Markdown makes it easy to add syntax to your plain text documents for readability and machine parsing. In this article, learn how to work with markdown in Python using the _python-markdown_ package.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.honeybadger.io%2Ffavicon-32x32.png%3F1756258543" width="32" height="32"&gt;
          honeybadger.io
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Thanks for reading 💜&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;I publish a monthly newsletter in which&lt;/a&gt; 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&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;ross while surfing the web.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;Connect with&lt;/a&gt; &lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;me through Twitter • LinkedIn • Github or&lt;/a&gt; send me an &lt;a href="//mailto:ravgeetdhillon@gmail.com"&gt;Email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://www.ravgeet.in/" rel="noopener noreferrer"&gt;Ravgeet&lt;/a&gt;, &lt;em&gt;Full Stack Developer and Technical Content Writer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>markdown</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Handling undo functions in rich text editors</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Fri, 01 Aug 2025 12:38:55 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/handling-undo-functions-in-rich-text-editors-2lgg</link>
      <guid>https://forem.com/ravgeetdhillon/handling-undo-functions-in-rich-text-editors-2lgg</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;However, implementing the undo/redo functionality is complicated. It requires an understanding of &lt;a href="https://www.geeksforgeeks.org/stack-data-structure/" rel="noopener noreferrer"&gt;&lt;strong&gt;data structures like Stack&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;In this article, you'll find out about the complexity of creating and maintaining the undo/redo functionality, and see how the &lt;a href="https://www.tiny.cloud/tinymce/" rel="noopener noreferrer"&gt;&lt;strong&gt;TinyMCE rich text editor&lt;/strong&gt;&lt;/a&gt; makes it easy.&lt;/p&gt;

&lt;p&gt;Read the full blog on &lt;a href="https://www.tiny.cloud/blog/undo-function-handling/" rel="noopener noreferrer"&gt;Tiny&lt;/a&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://www.tiny.cloud/blog/undo-function-handling/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.ctfassets.net%2Fs600jj41gsex%2FIICBKTjU9L8gUFJukA9Uf%2F962ce44ac46123e01dcb3472d516359b%2FImage_TInyMCE_Undo_Functionality2.jpg%3Fw%3D2560%26q%3D80%26fit%3Dscale" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://www.tiny.cloud/blog/undo-function-handling/" rel="noopener noreferrer" class="c-link"&gt;
            Handling undo function in rich text editors | TinyMCE
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Undo redo function implementation in a rich text editor can be complicated. Learn how to implement undo in TinyMCE with Undo Manager API
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABp1BMVEUAAAAzXf8cEv8zXv8wV%2F8yXP80Xv80X%2F80Xv8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8zXf8yXP8wW%2F8wWv8xXP84Yf9Ibv9Caf8yXf80Xv9Hbf9jg%2F%2BCnP%2Blt%2F%2FF0f%2Fi6P%2BEnf88ZP%2FBzv%2F4%2Bf%2F%2B%2Fv%2F%2F%2F%2F%2BLo%2F87Y%2F%2FT3P%2Ft8f%2Fh5%2F%2F7%2FP89Zf9ef%2F99mP%2BOpv9yj%2F9Ud%2F%2Fw8%2F94lP%2F09v%2Fy9f9cff8tWP8vWf9VeP%2Fv8v8vWv%2BBm%2F9df%2F8uWf8tWf%2Fu8f%2F3%2BP9qiP9Rdf9vjf%2BNpP%2BAmv9hgf8%2FZ%2F%2F8%2Ff%2Fs8P%2F9%2Ff%2Fb4%2F9AZ%2F%2F5%2Bv%2FJ1P9AaP97lv%2Fj6f%2FI1P%2Bouv%2BFnv9mhf9Kb%2F9BaP9Jb%2F86Yv8ia9EeAAAAQnRSTlMAAAAAAAAAAAIXPWmZye%2F966QyJ4jP7vzYRUbX0yos1f6NjtDl6dvc6N3izsaPhi0o0t9NR%2FDWljA0purMnWxAHAPzaT99AAAAAWJLR0RXfQrZHwAAAAd0SU1FB%2BUCCwE5E6xkUqMAAAD4SURBVBjTY2BgYOTg5OLm4eXjFxAUYmJkYGYRFhEVE3cCAmdnCUlGBkYpaRDbxdXN3cPTS0aWQU4eyPf28fXzDwgMclFQZFAC8r2CQ0LDwsPDI1ydlBl4gepdI6PComNiQQIqDBJOXnHxCYlJyb4pIAFVBjUn19S09IzMrOwciIC6U25eeFp%2BQWFyUbGfi5MGgyZYoKS0rLyissrLSQuoBShQHVNTW1ef6%2BzkpM2gAxIAgobGJiDfSZdBT8G1uaW1rb2j0xvIl9ZnMDD06urucXZ1AckbGbMyMJqoOYPYTuKmZuYWbEDPsVtaWfOb2tja2Ts4MjIwAABZYEOsFzWl7AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wMi0xMVQwMTo1NzoxOSswMTowMOldEocAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDItMTFUMDE6NTc6MTkrMDE6MDCYAKo7AAAAV3pUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHic4%2FIMCHFWKCjKT8vMSeVSAAMjCy5jCxMjE0uTFAMTIESANMNkAyOzVCDL2NTIxMzEHMQHy4BIoEouAOoXEXTyQjWVAAAAAElFTkSuQmCC"&gt;
          tiny.cloud
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Thanks for reading 💜&lt;/p&gt;




&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;I publish a monthly newsletter in which&lt;/a&gt; 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&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;ross while surfing the web.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;Connect with&lt;/a&gt; &lt;a href="https://www.tiny.cloud/" rel="noopener noreferrer"&gt;me through Twitter • LinkedIn • Github or&lt;/a&gt; send me an &lt;a href="mailto:ravgeetdhillon@gmail.com"&gt;Email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://www.ravgeet.in/" rel="noopener noreferrer"&gt;Ravgeet&lt;/a&gt;, &lt;em&gt;Full Stack Developer and Technical Content Writer&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>frontend</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🎬 Introducing Ravgeek: Dev Concepts in 60 Seconds</title>
      <dc:creator>Ravgeet Dhillon</dc:creator>
      <pubDate>Sat, 19 Jul 2025 02:41:33 +0000</pubDate>
      <link>https://forem.com/ravgeetdhillon/introducing-ravgeek-dev-concepts-in-60-seconds-54nj</link>
      <guid>https://forem.com/ravgeetdhillon/introducing-ravgeek-dev-concepts-in-60-seconds-54nj</guid>
      <description>&lt;p&gt;After years of writing code, debugging endlessly, and explaining APIs to teammates over coffee, I’ve finally taken the plunge into something new — &lt;strong&gt;bite-sized developer explainers on YouTube&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;📺 My new channel is called &lt;a href="https://www.youtube.com/@ravgeek" rel="noopener noreferrer"&gt;&lt;strong&gt;Ravgeek&lt;/strong&gt;&lt;/a&gt; (“t” dropped from my name)— and it's built around a simple idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Make technical concepts simple, fun, and fast.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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 &lt;strong&gt;under 60 seconds&lt;/strong&gt; — in a way that’s accessible to beginners and still fun for experienced devs.&lt;/p&gt;

&lt;p&gt;Here’s a video in which I explain - “What is prompt engineering”:&lt;/p&gt;

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

&lt;p&gt;You’ll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;⚡️ Rapid, to-the-point explanations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🎙️ Conversational storytelling (think devs talking over chai)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🎨 Animations, avatars, and a touch of humor&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This has been a passion project for me — combining my love for &lt;strong&gt;coding, storytelling, and design&lt;/strong&gt; — and I’m excited to finally share it with the world.&lt;/p&gt;

&lt;p&gt;👉 Check out the channel: &lt;a href="https://www.youtube.com/@ravgeek" rel="noopener noreferrer"&gt;youtube.com/@ravgeek&lt;/a&gt;&lt;br&gt;&lt;br&gt;
💬 And if you like what you see, hit that subscribe button and let me know what topic you'd like me to cover next.&lt;/p&gt;

&lt;p&gt;Let’s learn, laugh, and geek out together.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>youtube</category>
      <category>marketing</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
