<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tejas Patil</title>
    <description>The latest articles on DEV Community by Tejas Patil (@tejas164321).</description>
    <link>https://dev.to/tejas164321</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3828498%2Ff99fdd48-f37e-48e0-abdd-ba64eb016704.jpg</url>
      <title>DEV Community: Tejas Patil</title>
      <link>https://dev.to/tejas164321</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tejas164321"/>
    <language>en</language>
    <item>
      <title>How UPI Works: The ₹0 Magic Behind Every Scan-and-Pay (System Design)</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Thu, 29 Oct 2026 11:55:24 +0000</pubDate>
      <link>https://dev.to/tejas164321/how-upi-works-the-0-magic-behind-every-scan-and-pay-system-design-38kl</link>
      <guid>https://dev.to/tejas164321/how-upi-works-the-0-magic-behind-every-scan-and-pay-system-design-38kl</guid>
      <description>&lt;p&gt;&lt;em&gt;System Design Simplified — Part 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/MLhKVrv_gKU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The chai stall
&lt;/h2&gt;

&lt;p&gt;It's 8 AM. You're holding a cutting chai in one hand and your phone in the other. You scan a faded QR code on the counter, type four digits, and before Bablu looks up from his kettle, his phone says &lt;em&gt;₹20 received&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Two seconds. No card. No cash. No fee.&lt;/p&gt;

&lt;p&gt;In August 2026 alone, UPI handled a record 24.51 billion transactions, about 791 million a day. That's roughly 85% of India's digital payments and nearly half of all real-time payments on Earth.&lt;/p&gt;

&lt;p&gt;So what happened in those two seconds? Let's follow your ₹20.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the cast
&lt;/h2&gt;

&lt;p&gt;Every UPI payment has five characters, and each has exactly one job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You.&lt;/strong&gt; The person with the phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your UPI app.&lt;/strong&gt; PhonePe, Google Pay, Paytm. It does not hold your money. It's a messenger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your bank.&lt;/strong&gt; Where your money lives. The only one allowed to take ₹20 out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPCI.&lt;/strong&gt; The National Payments Corporation of India. It runs one central switch that every bank is plugged into. It holds no money either. It's the traffic cop at the junction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bablu's bank.&lt;/strong&gt; Where the ₹20 lands.&lt;/p&gt;

&lt;p&gt;One idea to remember: apps and NPCI are messengers. Only banks touch money.&lt;/p&gt;

&lt;h2&gt;
  
  
  The journey of ₹20, in motion
&lt;/h2&gt;

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

&lt;p&gt;&lt;em&gt;Purple = the request. Orange = the money. Green = done. Watch it loop once and the whole system makes sense.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice the order. Your bank is asked first. Money leaves your account before anyone asks Bablu's bank to accept it. That's deliberate: money stuck in transit can be reversed; money credited that was never debited cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, so what is Google Pay?
&lt;/h2&gt;

&lt;p&gt;This is the part most explainers skip.&lt;/p&gt;

&lt;p&gt;Google Pay, PhonePe, Paytm, CRED, Amazon Pay are not banks. NPCI has a strict rule: it talks only to banks. So how does an app made by Google reach the switch?&lt;/p&gt;

&lt;p&gt;It borrows a bank.&lt;/p&gt;

&lt;p&gt;Every UPI app partners with one or more licensed banks, called PSP banks (Payment Service Provider). The bank owns the actual connection to NPCI. It gets audited, holds the security certificates, and takes legal responsibility for every request the app sends. The app, officially a Third-Party Application Provider, rides on the bank's pipe.&lt;/p&gt;

&lt;p&gt;You've seen this without noticing. The &lt;code&gt;@okaxis&lt;/code&gt; in &lt;code&gt;ramesh@okaxis&lt;/code&gt; means Google Pay is using Axis Bank as its PSP. &lt;code&gt;@ybl&lt;/code&gt; is PhonePe on Yes Bank. &lt;code&gt;@paytm&lt;/code&gt; is Paytm's own handle. The part after &lt;code&gt;@&lt;/code&gt; names the app's partner bank, not yours.&lt;/p&gt;

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

&lt;p&gt;That's the twist: your money can sit in SBI while your Google Pay handle says Axis. Axis is the doorman who walks your request into NPCI. SBI still opens the vault.&lt;/p&gt;

&lt;p&gt;Why build it this way? NPCI would rather trust 50 regulated banks than vet 500 apps. If an app misbehaves, its PSP bank answers for it.&lt;/p&gt;

&lt;p&gt;There's a real-world reason the big apps now keep several PSP banks. In March 2020, PhonePe ran only on Yes Bank. When RBI put Yes Bank under a moratorium, NPCI blocked its handles, and PhonePe went down with it. Within months PhonePe added ICICI (&lt;code&gt;@ibl&lt;/code&gt;) and later Axis (&lt;code&gt;@axl&lt;/code&gt;). Today, if one partner bank is slow, the app quietly routes through another.&lt;/p&gt;

&lt;h2&gt;
  
  
  What NPCI actually does
&lt;/h2&gt;

&lt;p&gt;The switch isn't a black box. It does four jobs on every single payment.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Directory.&lt;/strong&gt; It keeps the phonebook. &lt;code&gt;bablutea@okaxis&lt;/code&gt; maps to a real bank and account number. You never see that mapping, and neither does the person paying you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Router.&lt;/strong&gt; It asks your bank to debit first, then asks the receiver's bank to credit, each with a strict timeout. A slow bank gets marked pending; the switch moves on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracker.&lt;/strong&gt; If a reply never comes back, NPCI doesn't guess. It marks the payment pending, runs a check-status call, and reverses the debit if the credit never landed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tally.&lt;/strong&gt; It records what every bank owes every other bank, all day. More on that below.&lt;/p&gt;

&lt;p&gt;NPCI talks only to banks, over private certified lines. It never holds money and never sees your PIN.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clever ideas inside
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your UPI ID is a nickname.&lt;/strong&gt; &lt;code&gt;ramesh@okaxis&lt;/code&gt; is like a saved contact. NPCI keeps the phonebook. Share it on a poster, switch banks later, the ID stays.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Your PIN never travels.&lt;/strong&gt; You type it into a locked box provided by NPCI, called the common library. It's encrypted right there on your phone, and only your bank has the key. Google Pay cannot read your PIN. Neither can NPCI. Neither can Axis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your phone is the password.&lt;/strong&gt; During setup, the app sends a silent SMS from your SIM. That binds your number, SIM and device together. There's no username. Your phone is the login; your PIN is the second factor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a switch, not a wallet.&lt;/strong&gt; A wallet has to hold your money. UPI never does. Money moves bank to bank, so there's no top-up, no middleman, no fee hiding in the spread.&lt;/p&gt;

&lt;h2&gt;
  
  
  When things go wrong
&lt;/h2&gt;

&lt;p&gt;Money debited, Bablu shrugs, nothing received. What happened?&lt;/p&gt;

&lt;p&gt;Your bank said yes and debited you. Then a message got lost between the switch and Bablu's bank. UPI doesn't guess. It marks the payment &lt;em&gt;pending&lt;/em&gt;, and NPCI runs a check-status call to learn the truth. If the credit never landed, a reversal goes to your bank and the ₹20 comes back, usually within hours.&lt;/p&gt;

&lt;p&gt;The promise: money is never in two places, and never nowhere. It's with you, with them, or in a tracked pending state that will resolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did the banks actually move money?
&lt;/h2&gt;

&lt;p&gt;Here's what almost nobody knows. When your bank debits ₹20 and Bablu's bank credits ₹20, the two banks haven't exchanged a rupee yet. Each just updated its own customer's balance.&lt;/p&gt;

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

&lt;p&gt;Think of two friends who keep paying for each other all week. They don't hand over cash every time; they keep a tally and settle the difference on Sunday. NPCI is the friend with the notebook. Several times a day it tells the Reserve Bank of India "Axis owes SBI ₹340 crore net," and one transfer settles millions of chai payments.&lt;/p&gt;

&lt;p&gt;That's why UPI is instant and free at the same time. The expensive part, moving real money between banks, happens only a few times a day, in bulk.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does one switch survive 791 million hits a day?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nobody waits for anyone.&lt;/strong&gt; Every hop has a strict timeout. A slow bank gets marked pending; the switch moves on. Slow banks hurt their own customers, not the country.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The switch is stateless, copied many times.&lt;/strong&gt; It only routes messages, so dozens of identical copies sit behind a load balancer. Any copy can handle any payment. One dies, nobody notices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every message has an ID that can't be reused.&lt;/strong&gt; If your app resends because the network blinked, the switch sees the same transaction ID and says "already done" instead of charging you twice. This is called idempotency. It's the single most important idea in payments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things to remember
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Apps and NPCI are messengers. Only banks hold money.&lt;/li&gt;
&lt;li&gt;Google Pay isn't a bank. It reaches NPCI through a partner PSP bank, named by the &lt;code&gt;@&lt;/code&gt; in your UPI ID.&lt;/li&gt;
&lt;li&gt;Your PIN is encrypted on your phone; only your bank can read it.&lt;/li&gt;
&lt;li&gt;Debit first, credit second. Pending is a tracked state, not a bug.&lt;/li&gt;
&lt;li&gt;Banks settle in batches, which is why instant can also be free.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next in the series
&lt;/h2&gt;

&lt;p&gt;Part 2: How Google Pay handles the &lt;em&gt;Payment Pending&lt;/em&gt; screen without losing your money. Retries, timeouts and idempotency, told through one stuck ₹500.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Numbers from NPCI's monthly statistics for August 2026. Bablu Tea Stall is fictional; the system is not.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>fintech</category>
      <category>upi</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your phone is not an RTX 4090. This open model knows that.</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Fri, 16 Oct 2026 12:21:11 +0000</pubDate>
      <link>https://dev.to/tejas164321/your-phone-is-not-an-rtx-4090-this-open-model-knows-that-5hi7</link>
      <guid>https://dev.to/tejas164321/your-phone-is-not-an-rtx-4090-this-open-model-knows-that-5hi7</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Every Minecraft PE player knows this moment: you install a gorgeous shader with godrays and glassy water, load your world, and your game turns into a slideshow.&lt;/p&gt;

&lt;p&gt;I run &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.piglixmcmods.app" rel="noopener noreferrer"&gt;MCPE Shaders and Textures&lt;/a&gt;&lt;/strong&gt;, an Android app with [N]+ shaders and texture packs and [8,000+] installs. The #1 problem my players have isn't &lt;em&gt;finding&lt;/em&gt; a shader. It's finding one their phone can actually run.&lt;/p&gt;

&lt;p&gt;So this week I built &lt;strong&gt;ShaderFit&lt;/strong&gt;. You type what you want, like &lt;em&gt;"clear water, no lag"&lt;/em&gt; or &lt;em&gt;"cozy sunset for building,"&lt;/em&gt; and it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📱 &lt;strong&gt;Checks your phone:&lt;/strong&gt; GPU, RAM and Minecraft version, automatically&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Finds the look:&lt;/strong&gt; matches your words to shader descriptions &lt;em&gt;and&lt;/em&gt; preview screenshots&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Predicts lag:&lt;/strong&gt; learns from real players' "Smooth / Laggy" taps&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Explains the pick:&lt;/strong&gt; "Runs well on Adreno 610. Set render distance to 8."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Who pays?&lt;/strong&gt; The app is free with ads. &lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://play.google.com/store/apps/details?id=com.piglixmcmods.app" rel="noopener noreferrer"&gt;Try it on Google Play&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/SzvdaDtbPTM" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;ShaderFit is a &lt;strong&gt;new repo built during the challenge (Oct 12–18)&lt;/strong&gt;. The catalog app existed before.&lt;/p&gt;

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

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EmbeddingGemma 2&lt;/strong&gt; (Google's new open model) puts text and images in the same space, so &lt;em&gt;"crystal clear water"&lt;/em&gt; finds the shader whose &lt;strong&gt;screenshot&lt;/strong&gt; shows clear water, even if its description never says it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TabPFN&lt;/strong&gt; predicts the chance a shader runs smooth on &lt;em&gt;your&lt;/em&gt; phone from [N] real player reports, with no training needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma 4&lt;/strong&gt; writes the one-line tip.&lt;/li&gt;
&lt;li&gt;Everything runs on one small server on &lt;strong&gt;Render&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Did it work?
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phone&lt;/th&gt;
&lt;th&gt;Most popular shader&lt;/th&gt;
&lt;th&gt;ShaderFit's pick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[Budget phone]&lt;/td&gt;
&lt;td&gt;[30 ] FPS 🐌&lt;/td&gt;
&lt;td&gt;[70 ]FPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Mid-range phone]&lt;/td&gt;
&lt;td&gt;[45 ] FPS&lt;/td&gt;
&lt;td&gt;[85 ] FPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Flagship phone]&lt;/td&gt;
&lt;td&gt;[58 ] FPS&lt;/td&gt;
&lt;td&gt;[94 ] FPS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right shader was in the top 5 for &lt;strong&gt;[100]%&lt;/strong&gt; of real player requests, up from &lt;strong&gt;[80]%&lt;/strong&gt; with my old keyword search.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What still breaks:&lt;/strong&gt; [1–2 honest limits, e.g. rare GPUs have little data yet.]&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free apps can't pay per search.&lt;/strong&gt; A closed AI API would cost [amount] a month for my players. Open models cost [amount].&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It sees screenshots, not just words.&lt;/strong&gt; That's how players actually pick shaders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Player data stays mine.&lt;/strong&gt; No outside AI company gets anyone's device info.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I'm allowed to sell it.&lt;/strong&gt; EmbeddingGemma 2 and Gemma 4 are Apache 2.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My First User
&lt;/h2&gt;

&lt;p&gt;[Real player feedback in 2–3 lines: how many tried it, one real quote (with permission), what they asked for next.]&lt;/p&gt;

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

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

&lt;p&gt;Best Use of Gemma · Best Use of TabPFN · Best Use of Render&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Unofficial app, not affiliated with Mojang or Microsoft.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>The Last Time I Googled an Error Message</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:18:27 +0000</pubDate>
      <link>https://dev.to/tejas164321/the-last-time-i-googled-an-error-message-5fph</link>
      <guid>https://dev.to/tejas164321/the-last-time-i-googled-an-error-message-5fph</guid>
      <description>&lt;p&gt;I don't remember the last time I pasted an error into Google.&lt;/p&gt;

&lt;p&gt;Not the date. I mean the habit left and I didn't notice it go. Something I did five times a day just stopped.&lt;/p&gt;

&lt;p&gt;Here's the whole six years.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2019. Google it.&lt;/strong&gt;&lt;br&gt;
Copy the error. Delete the parts with your name in them. Paste. Stack Overflow, result one. Scroll past the 2013 answer everyone says is outdated. Find the one with 400 upvotes sitting fourth.&lt;/p&gt;

&lt;p&gt;Forty minutes. But you understood the fix by the time you shipped it, because you had to read four wrong answers to find the right one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2020. Everyone goes home.&lt;/strong&gt;&lt;br&gt;
Same ritual. Worse chair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nov 2022. ChatGPT.&lt;/strong&gt;&lt;br&gt;
We used it to talk like a pirate for two weeks. Then somebody pasted an error in as a joke.&lt;/p&gt;

&lt;p&gt;It answered &lt;em&gt;your&lt;/em&gt; error. Your variable names. Your file. No "closed as duplicate."&lt;/p&gt;

&lt;p&gt;That's the moment. Not the launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2023. Paste, patch, repeat.&lt;/strong&gt;&lt;br&gt;
It was bad. Invented functions. Wrong library versions. Fix one error, get two.&lt;/p&gt;

&lt;p&gt;Still faster than being wrong alone. So Google went second. Then optional. Then gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2024. Too long.&lt;/strong&gt;&lt;br&gt;
"Message exceeds limit." You chopped your own project into snacks for a model with a small mouth. More time deciding what to paste than actually coding.&lt;/p&gt;

&lt;p&gt;Then context windows got huge and you just pasted everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2025. It reads the repo.&lt;/strong&gt;&lt;br&gt;
Cursor. No more describing your folder structure like a dream you had. The ask changed from "how do I do X" to "do X."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nov 2025. Agents.&lt;/strong&gt;&lt;br&gt;
Antigravity ships with Gemini 3. Claude Code lives in the terminal, and somehow that plain black rectangle became the serious one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2026. You.&lt;/strong&gt;&lt;br&gt;
Describe the thing. Go make chai. Come back. Review a diff.&lt;/p&gt;

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

&lt;p&gt;Stack Overflow went from over 200,000 questions a month to under 50,000 by late 2025. Lowest since May 2009, ten months after it launched.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part that bugs me
&lt;/h2&gt;

&lt;p&gt;I'm not doing the "learn real coding" speech. I use these every day.&lt;/p&gt;

&lt;p&gt;But those forty minutes in 2019 were annoying &lt;strong&gt;and&lt;/strong&gt; they were the entire learning mechanism. Four wrong answers taught you the shape of the problem. Now you get the right one in nine seconds and learn nothing, because nothing had to be survived.&lt;/p&gt;

&lt;p&gt;And there's a worse one. All of that AI knowledge came from people posting questions and answers in public. We stopped posting. Every bug since 2024 gets solved in a private chat window and then deleted.&lt;/p&gt;

&lt;p&gt;We're all debugging alone in a room and calling it a community.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. When did &lt;em&gt;your&lt;/em&gt; default switch from Google to a chat box?&lt;/strong&gt; Not the launch date. The specific moment you noticed.&lt;/p&gt;

&lt;p&gt;Mine was a library whose docs were wrong. Google gave me four threads about a different version. I pasted the stack trace into a chat window and it got it in one, and I never really went back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What's the last thing you actually googled?&lt;/strong&gt; Be honest.&lt;/p&gt;

&lt;p&gt;Mine was "why is my laptop fan so loud." Not a coding question. That's kind of the point.&lt;/p&gt;

&lt;p&gt;Drop yours below. I genuinely want to see how differently we all walked the same six years.&lt;/p&gt;




&lt;p&gt;If you've been coding since before 2022, you lived this whole timeline. If you started after, you walked into the middle of it and everyone acted like it had always been this way.&lt;/p&gt;

&lt;p&gt;Either way, I'd like to hear which year you came in at.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My Code Works. An AI Wrote It. Neither of Us Knows Why.</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Wed, 02 Sep 2026 12:17:20 +0000</pubDate>
      <link>https://dev.to/tejas164321/my-code-works-an-ai-wrote-it-neither-of-us-knows-why-1980</link>
      <guid>https://dev.to/tejas164321/my-code-works-an-ai-wrote-it-neither-of-us-knows-why-1980</guid>
      <description>&lt;p&gt;here's a meme we've all posted some version of.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"My code works and I have no idea why."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Classic. Universal. Every developer since 1970 has felt it.&lt;/p&gt;

&lt;p&gt;But it changed this year, and nobody really announced it. Now it goes:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The AI wrote code that works. Neither of us knows why."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Same joke. Completely different problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The old version was funny because it was temporary
&lt;/h2&gt;

&lt;p&gt;Back then, "I don't know why it works" meant you'd get there eventually. You wrote it. You'd tripped over something, and if you sat with it for twenty minutes you'd figure out what.&lt;/p&gt;

&lt;p&gt;The confusion was yours. So was the code. So was the fix.&lt;/p&gt;

&lt;p&gt;The new version is different. You didn't write it, you can't debug it, and there is no twenty-minute session that gets you there, because you were never in the loop to begin with.&lt;/p&gt;

&lt;p&gt;You approved it. That's a different verb.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  The numbers are genuinely wild
&lt;/h2&gt;

&lt;p&gt;By 2026, 92% of US-based developers are expected to use AI coding tools daily, and 41% of all code globally is projected to be AI-generated. Roughly 40% of junior developers deploy code without fully understanding it.&lt;/p&gt;

&lt;p&gt;Sit with that last one. Not "wrote without understanding." &lt;strong&gt;Deployed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then there's my favourite study, the one nobody wants to talk about: developers believed AI made them about 20% faster, and measurement showed they were actually around 19% slower.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnf2ot2syphvcgunt3czp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnf2ot2syphvcgunt3czp.png" alt=" " width="800" height="432"&gt;&lt;/a&gt;&lt;br&gt;
We're not just shipping code we don't understand. We're wrong about whether it's even helping.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meanwhile, at the top
&lt;/h2&gt;

&lt;p&gt;Andrej Karpathy, who coined "vibe coding" in the first place, called the term passé back in February. His argument was that the frontier had already moved past autocomplete to agents that plan, test and correct themselves across whole codebases.&lt;/p&gt;

&lt;p&gt;The guy who named it retired it before most of us finished arguing about it.&lt;/p&gt;

&lt;p&gt;And underneath, junior software developer employment has now declined for 33 consecutive months.&lt;/p&gt;

&lt;p&gt;So the entry-level job is disappearing at the exact moment the main skill being demanded is judgement, which is the one thing you can only get by doing the entry-level job.&lt;/p&gt;

&lt;p&gt;Great. Love that for us.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part I actually think about
&lt;/h2&gt;

&lt;p&gt;I'm a final-year student. I'm supposed to be the target market for all of this.&lt;/p&gt;

&lt;p&gt;And the honest version is that AI is incredible when I already know what I'm looking at, and quietly dangerous when I don't. Same tool. The difference is entirely me.&lt;/p&gt;

&lt;p&gt;When I understand the domain, it's the best pair programmer I've ever had. When I don't, it's a very confident stranger handing me something and walking away.&lt;/p&gt;

&lt;p&gt;The trap isn't that AI writes bad code. It writes fine code, usually. The trap is that working code feels like finished code, and those have never been the same thing.&lt;/p&gt;

&lt;p&gt;My rule now, for whatever it's worth:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I can't explain why the code works, I haven't finished. I've just stopped.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "read every line." Nobody does that and you know it. Just: can I say out loud what it does and why that's the right approach? If not, I'm not done, no matter how green the tests are.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;I want the honest answers, not the LinkedIn ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the worst thing you've shipped that you didn't fully understand?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'll start. I once merged a fix for a bug I never actually reproduced. It worked. It's still in production. I still don't know if it fixed the bug or just moved it somewhere I haven't looked yet.&lt;/p&gt;

&lt;p&gt;Drop yours below. No judgement, we're all in the same boat and the boat was generated by an LLM.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources: &lt;a href="https://daily.dev/blog/vibe-coding-2026-ai-changing-how-developers-write-code/" rel="noopener noreferrer"&gt;daily.dev on vibe coding in 2026&lt;/a&gt; · &lt;a href="https://www.forbes.com/sites/josipamajic/2026/08/09/coding-jobs-vanish-for-juniors-as-ai-reshapes-career-path/" rel="noopener noreferrer"&gt;Forbes on entry-level coding jobs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Pawscript - Your Dog's Letter to You</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Mon, 17 Aug 2026 10:57:21 +0000</pubDate>
      <link>https://dev.to/tejas164321/pawscript-your-dogs-letter-to-you-3hl9</link>
      <guid>https://dev.to/tejas164321/pawscript-your-dogs-letter-to-you-3hl9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-08-13"&gt;Weekend Challenge: Dog Days Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Pawscript. Upload a photo of your dog, optionally with a bark, whine, or sigh, and Gemini reads the actual photo (and sound, if given) and writes a short, warm letter in your dog's own voice, along with a few honest "Tail Tell" observations about their visible posture and expression.&lt;/p&gt;

&lt;p&gt;My goal was to avoid the usual "upload a photo → get a random label" pattern that many pet apps use, and instead build something that is actually grounded in the specific photo you provide. So if you scan the same dog twice, the result can genuinely be different rather than feeling like a copy-paste response.&lt;/p&gt;

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

&lt;p&gt;Live: &lt;a href="https://pawscript.ai.studio" rel="noopener noreferrer"&gt;https://pawscript.ai.studio&lt;/a&gt;&lt;/p&gt;

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

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Tejas164321" rel="noopener noreferrer"&gt;
        Tejas164321
      &lt;/a&gt; / &lt;a href="https://github.com/Tejas164321/Pawscript" rel="noopener noreferrer"&gt;
        Pawscript
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Pawscript. Upload a photo of your dog - optionally with a bark, whine, or sigh - and Gemini reads the actual photo (and sound, if given) and writes a short, warm letter in your dog's own voice, plus a few honest "Tail Tell" observations about their visible posture and expression.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;I started with a UI I liked, a five-paw "scanning" progress meter with a breathing/pulse animation, and rebuilt the logic underneath so it actually does something.&lt;/p&gt;

&lt;p&gt;The core is a single multimodal call to Gemini. The uploaded photo, along with optional audio, is sent with a system instruction that tells the model to reference specific visual details it can actually see in that photo, such as the coat, posture, setting, and expression. This helps keep the letters specific to the image instead of making them sound generic. I also enforced structured JSON output with &lt;code&gt;letter&lt;/code&gt;, &lt;code&gt;mood_tag&lt;/code&gt;, &lt;code&gt;observations&lt;/code&gt;, and &lt;code&gt;used_audio&lt;/code&gt;, so the UI never has to guess how to parse the response.&lt;/p&gt;

&lt;p&gt;The trickiest part was the optional audio path. I had to send the image and inline base64 audio in the same request and ask Gemini to combine the acoustic tone, such as a bright yip or a low grumble, with the visual details when writing the letter. I also made sure it never invents sound characteristics when no audio is provided.&lt;/p&gt;

&lt;p&gt;I tied the paw-progress animation to the real fetch lifecycle instead of using a fixed timer, so the scanning moment is based on the actual request rather than being purely decorative.&lt;/p&gt;

&lt;p&gt;If shipped, letters can optionally be read aloud using ElevenLabs voice narration.&lt;/p&gt;

&lt;p&gt;Nothing uploaded is stored. Photos and audio go directly to Gemini for analysis and aren't kept.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Use of Google AI&lt;/strong&gt; — Gemini multimodal vision + audio understanding with enforced structured output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs&lt;/strong&gt; — if you shipped voice narration, keep this line; if not, delete it.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy Dog Days! 🐾&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>I Chased a Data Bug for an Afternoon. It Was Never About the Data.</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:11:24 +0000</pubDate>
      <link>https://dev.to/tejas164321/i-chased-a-data-bug-for-an-afternoon-it-was-never-about-the-data-3j6</link>
      <guid>https://dev.to/tejas164321/i-chased-a-data-bug-for-an-afternoon-it-was-never-about-the-data-3j6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the thing about the bug I'm about to describe: for the first hour, I was completely sure I knew where it lived. I was wrong, and I want to walk you through exactly how wrong, because the actual location was almost insulting in how little sense it made until suddenly it made total sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx.dev&lt;/a&gt; is an open-source browser for the npm registry. One of its features lets you compare two packages side by side — versions, install size, dependencies, the works. Somebody filed a bug: compare &lt;code&gt;tinyclip&lt;/code&gt; against anything, and it would report &lt;code&gt;tinyclip&lt;/code&gt; stuck at version &lt;code&gt;0.0.1&lt;/code&gt; — its very first-ever published version — when the real latest was &lt;code&gt;0.1.8&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My first thought was the obvious one: the comparison logic is reading the wrong field. Maybe it's grabbing the first published version instead of the &lt;code&gt;latest&lt;/code&gt; dist-tag. Confident, reasonable, and completely wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing the Wrong Suspect
&lt;/h2&gt;

&lt;p&gt;I went into the comparison composable expecting to find some sloppy array indexing or a fallback that defaulted to the wrong thing. Instead I found totally reasonable code: fetch the package's registry data, read &lt;code&gt;dist-tags.latest&lt;/code&gt;, done. No obvious bug. No off-by-one. Nothing.&lt;/p&gt;

&lt;p&gt;This is the part of debugging nobody puts in the tutorial: the moment where your working theory just quietly stops being true, and you have to admit you don't know where the bug is anymore.&lt;/p&gt;

&lt;p&gt;So I went one level down, into the actual fetch function behind that call. And that's where things got strange.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Parameter That Wasn't Doing Anything
&lt;/h2&gt;

&lt;p&gt;Buried in the fetch wrapper was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;_ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FETCH_CACHE_DEFAULT_TTL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An underscore-prefixed parameter. If you've spent any time in TypeScript, you know what that prefix means: "accepted, but deliberately unused." Someone had designed a whole caching contract around a configurable time-to-live — and on the client, it was accepted into the function signature and then never touched again.&lt;/p&gt;

&lt;p&gt;Right next to it, the actual caching behavior was hardcoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I want to be honest about my reaction to this, because it wasn't "aha, found it" right away. It was closer to "okay but that can't be the whole thing, this is a comparison page, not a caching page." I almost moved on. The bug I was hunting was about wrong data, not stale data — or so I thought, before I remembered that from a browser's perspective, those are frequently the exact same problem wearing different clothes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;force-cache&lt;/code&gt; Is a Trap
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;force-cache&lt;/code&gt; tells the browser one very specific thing: if any cached response already exists for this URL, use it — don't bother checking whether it's still fresh. That's different from the browser's normal behavior, which checks &lt;code&gt;Cache-Control&lt;/code&gt; headers and quietly revalidates once something goes stale.&lt;/p&gt;

&lt;p&gt;So here's what was actually happening: the very first time your browser ever fetched &lt;code&gt;tinyclip&lt;/code&gt;'s registry data — however long ago that was — it locked that response in. New versions could publish on npm every week after that, and your browser would never know, because it had been explicitly told not to ask again.&lt;/p&gt;

&lt;p&gt;It wasn't a data bug. It was a bug about &lt;em&gt;when the browser was allowed to ask for new data&lt;/em&gt;, disguised as a data bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Sure I Wasn't Just Telling Myself a Nice Story
&lt;/h2&gt;

&lt;p&gt;Before I trusted this theory, I checked what &lt;code&gt;registry.npmjs.org&lt;/code&gt; actually sends back on a real request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;cache-control: public, max-age=300
etag: "..."
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry was already doing its job — five-minute freshness window, an ETag for cheap revalidation after that. The app just wasn't listening to any of it. That was the moment the theory stopped being a theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- cache: 'force-cache',
&lt;/span&gt;&lt;span class="gi"&gt;+ cache: 'default',
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line, in two places (there's a client branch and an identical server-fallback branch — both needed the same fix, or the bug would've just moved house). &lt;code&gt;'default'&lt;/code&gt; means: respect what the server actually told you about freshness, instead of assuming a months-old response is still gospel.&lt;/p&gt;

&lt;p&gt;I ran the full test suite afterward — 1,000+ tests, all green — and checked the network panel to confirm requests were genuinely round-tripping instead of resolving instantly from a frozen local copy.&lt;/p&gt;

&lt;p&gt;PR, if you want to see the actual diff: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3156" rel="noopener noreferrer"&gt;npmx-dev/npmx.dev#3156&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Actually Tell Someone About This
&lt;/h2&gt;

&lt;p&gt;If a bug report describes wrong-looking data, "check the data logic" is a completely reasonable first move — it's just not the &lt;em&gt;only&lt;/em&gt; move. Data can be wrong because the logic is wrong, or because the logic is right and it's being fed a version of reality that's frozen in time. Those look identical from the outside. They are not identical to fix.&lt;/p&gt;

&lt;p&gt;And the real lesson, the one I keep coming back to: an unused parameter isn't nothing. It's a question the codebase is quietly still asking, that nobody's gotten around to answering. &lt;code&gt;_ttl&lt;/code&gt; sat there the whole time, patiently doing nothing, waiting for someone to notice that it should have been doing something.&lt;/p&gt;

&lt;p&gt;I noticed. Eventually.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>debugging</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Vada Pav - An Interactive Landing Page for Mumbai's Favorite Street Food</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Tue, 11 Aug 2026 11:27:27 +0000</pubDate>
      <link>https://dev.to/tejas164321/vada-pav-an-interactive-landing-page-for-mumbais-favorite-street-food-3eli</link>
      <guid>https://dev.to/tejas164321/vada-pav-an-interactive-landing-page-for-mumbais-favorite-street-food-3eli</guid>
      <description>&lt;p&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2026-07-29"&gt;Frontend Challenge - Comfort Food Edition, Perfect Landing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An interactive landing page for vada pav — not a restaurant, not a recipe blog, a love letter to one dish that means something specific to me.&lt;/p&gt;

&lt;p&gt;Vada pav is comfort food that people from Maharashtra will defend with real conviction — everyone has an opinion about the right ratio of garlic chutney to green, whether onion belongs on it, and which cart does it best. I wanted the page to feel like the dish itself: warm, a little chaotic, and specific rather than generic.&lt;/p&gt;

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

&lt;p&gt;The site has a hero section with an animated illustration (continuous steam, twinkling string lights evoking a stall at dusk, a slow monsoon-rain layer in the background), a personal story section, a four-moment "Day With It" timeline walking through the same dish at different times of day, and an interactive centerpiece: the &lt;strong&gt;Vada Pav Builder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The builder lets you toggle five real toppings — green chutney, dry garlic chutney, a fried chili, raw onion, and sev — and watch them stack in the correct order on an SVG sandwich, regardless of what order you click them in. As you build, a live-generated sentence describes your exact combination in plain English, and an animated heat meter tracks how spicy your choices are.&lt;/p&gt;

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

&lt;p&gt;Live site: &lt;a href="https://mumbaivadapav.netlify.app/" rel="noopener noreferrer"&gt;https://mumbaivadapav.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CodePen: &lt;a href="https://codepen.io/editor/lvjstkex-the-vuer/pen/019ff081-af80-78e8-8213-5a8a3f37f6cb" rel="noopener noreferrer"&gt;https://codepen.io/editor/lvjstkex-the-vuer/pen/019ff081-af80-78e8-8213-5a8a3f37f6cb&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;I wanted to avoid the landing-page defaults I kept seeing myself reach for — a cream background with a single warm accent, or a generic hero with a stock gradient. Since vada pav already has a strong, specific visual identity, I built the palette directly from the dish instead: a deep frying-oil brown-black (the kadhai it's cooked in), turmeric gold, chili red, and curry-leaf green, with a warm cream reserved for the reading sections.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Vada Pav Builder&lt;/strong&gt; is the part I'm most proud of. Under the hood it's plain vanilla JavaScript — a state object mapping each ingredient to a boolean, rendered into a fixed-order stack using CSS &lt;code&gt;order&lt;/code&gt; so the sandwich always assembles correctly no matter which order you click ingredients in. Tying it to three things at once — the visual stack, a live-generated natural-language sentence, and an animated heat meter — is what took it from "a toggle list" to something that actually feels like assembling food.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;"Day With It" timeline&lt;/strong&gt; walks through the dish at four real moments (a station-platform grab, after school, a monsoon evening under a stall's tarpaulin, and the last cart open at night) — a real sequence, not just a stylistic list, which is why it's numbered/ordered rather than presented as flat cards.&lt;/p&gt;

&lt;p&gt;On accessibility: every ingredient toggle is a real &lt;code&gt;&amp;lt;button aria-pressed&amp;gt;&lt;/code&gt;, not a clickable div; an &lt;code&gt;aria-live&lt;/code&gt; region announces each addition and removal for screen reader users; all interactive elements have visible keyboard focus states; and every animation — the steam, the string lights, the stack's spring-in motion, the rain layer — is disabled cleanly via a &lt;code&gt;prefers-reduced-motion&lt;/code&gt; check, both in CSS and in the JavaScript that spawns the sparkle effects.&lt;/p&gt;

&lt;p&gt;The whole thing is a single self-contained HTML file — no build step, no framework — which felt like the right scope for a food-themed love letter rather than a full application.&lt;/p&gt;

&lt;p&gt;Thanks for reading — go find your own fifteen-rupee miracle. 🌶️&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Fixing a Silent Cache Bug in npmx.dev</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Mon, 10 Aug 2026 05:32:55 +0000</pubDate>
      <link>https://dev.to/tejas164321/fixing-a-silent-cache-bug-in-npmxdev-2bh6</link>
      <guid>https://dev.to/tejas164321/fixing-a-silent-cache-bug-in-npmxdev-2bh6</guid>
      <description>&lt;p&gt;This is a submission for DEV's Summer Bug Smash: Clear the Lineup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmx.dev" rel="noopener noreferrer"&gt;npmx.dev&lt;/a&gt; is a fast browser for the npm registry — you&lt;br&gt;
can look up any package or compare two side by side. On the compare&lt;br&gt;
page, package version numbers were sometimes just... wrong. Not&lt;br&gt;
crashing-wrong, just quietly stale — showing a version that npm had&lt;br&gt;
long since replaced.&lt;/p&gt;

&lt;p&gt;The bug had already been reported (&lt;a href="https://github.com/npmx-dev/npmx.dev/issues/1832" rel="noopener noreferrer"&gt;issue #1832&lt;/a&gt;):&lt;br&gt;
comparing &lt;code&gt;tinyclip&lt;/code&gt; and &lt;code&gt;copy-paste&lt;/code&gt; showed &lt;code&gt;tinyclip&lt;/code&gt; stuck at its&lt;br&gt;
very first published version, &lt;code&gt;0.0.1&lt;/code&gt;, when the real latest was&lt;br&gt;
&lt;code&gt;0.1.8&lt;/code&gt;. The original screenshots in that issue show the mismatch&lt;br&gt;
clearly next to what the npm registry itself reports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wo1504lvsyuqnj92wr0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1wo1504lvsyuqnj92wr0.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The original bug, as reported in issue #1832 — tinyclip stuck at v0.0.1 instead of the real latest, v0.1.8.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The kind of bug that's easy to shrug off as "just a display issue" —&lt;br&gt;
but on a site whose entire purpose is showing accurate package data, a&lt;br&gt;
silently wrong version number undermines the one thing the tool exists&lt;br&gt;
to do.&lt;/p&gt;
&lt;h2&gt;
  
  
  Chasing it down
&lt;/h2&gt;

&lt;p&gt;My first assumption was that the comparison logic itself was buggy —&lt;br&gt;
maybe it was reading the wrong field, or resolving &lt;code&gt;latest&lt;/code&gt; incorrectly.&lt;br&gt;
That assumption was wrong, and figuring out &lt;em&gt;why&lt;/em&gt; it was wrong is most&lt;br&gt;
of this story.&lt;/p&gt;

&lt;p&gt;Tracing the data flow: the compare page calls a composable called&lt;br&gt;
&lt;code&gt;usePackageComparison&lt;/code&gt;, which for each package calls &lt;code&gt;$npmRegistry(...)&lt;/code&gt;&lt;br&gt;
to fetch that package's full registry data — including which version is&lt;br&gt;
tagged &lt;code&gt;latest&lt;/code&gt;. That function is a thin wrapper around another&lt;br&gt;
composable, &lt;code&gt;useCachedFetch()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's where it got interesting. &lt;code&gt;useCachedFetch()&lt;/code&gt; takes a &lt;code&gt;ttl&lt;/code&gt;&lt;br&gt;
(time-to-live) argument — a signal for how long a cached response&lt;br&gt;
should be considered fresh. Except on the client, the parameter was&lt;br&gt;
named &lt;code&gt;_ttl&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="nx"&gt;_ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FETCH_CACHE_DEFAULT_TTL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The underscore prefix is a convention for "this parameter is&lt;br&gt;
intentionally unused." It was accepted, and then never referenced again&lt;br&gt;
in the function body. Whatever caching strategy the &lt;code&gt;ttl&lt;/code&gt; value was&lt;br&gt;
supposed to control, it wasn't actually controlling anything on the&lt;br&gt;
client.&lt;/p&gt;

&lt;p&gt;Instead, the client branch hardcoded this:&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;defaultFetchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;force-cache&lt;/code&gt; is a browser fetch option with a specific meaning: if any&lt;br&gt;
cached response already exists for a URL, use it — don't check whether&lt;br&gt;
it's still fresh. That's different from the browser's normal caching&lt;br&gt;
behavior, which checks &lt;code&gt;Cache-Control&lt;/code&gt; headers and revalidates once a&lt;br&gt;
response goes stale.&lt;/p&gt;

&lt;p&gt;So once a browser had fetched a package's registry data one time —&lt;br&gt;
whenever that first happened, possibly weeks earlier — it would keep&lt;br&gt;
serving that exact same response indefinitely. New versions could be&lt;br&gt;
published to npm and the app would never know, because the browser was&lt;br&gt;
never allowed to ask again.&lt;/p&gt;

&lt;p&gt;I checked what the npm registry actually sends back, to confirm this&lt;br&gt;
wasn't just a theory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;cache-control: public, max-age=300
etag: "..."
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0yk4pl7fhnxb8wrnbnhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0yk4pl7fhnxb8wrnbnhu.png" alt=" " width="799" height="413"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The registry's actual response headers — it was already telling clients how to cache correctly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The registry is already telling clients exactly how to cache correctly&lt;br&gt;
— treat this as fresh for 5 minutes, then use the ETag to check back&lt;br&gt;
efficiently. The app just wasn't listening to that instruction.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The fix itself is small — which is sometimes a sign you've found the&lt;br&gt;
actual root cause rather than papered over a symptom. In&lt;br&gt;
&lt;code&gt;app/composables/useCachedFetch.ts&lt;/code&gt;, both places that hardcoded&lt;br&gt;
&lt;code&gt;cache: 'force-cache'&lt;/code&gt; (the client branch, and a server-side fallback&lt;br&gt;
branch with identical logic) now use:&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;defaultFetchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Parameters&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;$fetch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;'default'&lt;/code&gt; tells the browser to follow standard HTTP caching rules —&lt;br&gt;
respect the registry's &lt;code&gt;max-age&lt;/code&gt;, and revalidate via ETag once it&lt;br&gt;
expires, rather than assuming a response is correct forever regardless&lt;br&gt;
of age.&lt;/p&gt;

&lt;p&gt;PR: &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3156" rel="noopener noreferrer"&gt;https://github.com/npmx-dev/npmx.dev/pull/3156&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ran the existing test suite — all 1,000+ unit tests across 55+ files
passed with zero failures, confirming the change doesn't break
anything else relying on this composable&lt;/li&gt;
&lt;li&gt;Loaded the compare page against the fix and confirmed it renders
cleanly with no console errors&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inspected the request in DevTools and confirmed a full, real network&lt;br&gt;
round-trip completes end to end (DNS lookup, connection, waiting for&lt;br&gt;
server response, content download) rather than resolving instantly —&lt;br&gt;
consistent with the browser actually being allowed to talk to the&lt;br&gt;
registry instead of only ever reading a frozen local copy&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added a regression test (&lt;code&gt;test/unit/app/composables/use-cached-fetch.spec.ts&lt;/code&gt;) confirming the fetch call uses &lt;code&gt;cache: 'default'&lt;/code&gt; — ran the full suite afterward: 84 test files, 1,723 tests, zero failures&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzstazwlzs95rv805gp3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzstazwlzs95rv805gp3h.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A full network round-trip completing after the fix — not an instant cache hit.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd take from this
&lt;/h2&gt;

&lt;p&gt;The most useful signal in this whole investigation wasn't a stack trace&lt;br&gt;
or an error message — it was an unused function parameter. &lt;code&gt;_ttl&lt;/code&gt; was&lt;br&gt;
evidence that someone had designed a caching contract around a real TTL&lt;br&gt;
concept, and a later implementation detail (hardcoding &lt;code&gt;force-cache&lt;/code&gt;)&lt;br&gt;
quietly bypassed it without anyone updating the signature to match.&lt;br&gt;
Dead parameters are worth pausing on — they're often a fossil record of&lt;br&gt;
intent the code no longer honors.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Fixes &lt;a href="https://github.com/npmx-dev/npmx.dev/issues/1832" rel="noopener noreferrer"&gt;npmx-dev/npmx.dev#1832&lt;/a&gt; via &lt;a href="https://github.com/npmx-dev/npmx.dev/pull/3156" rel="noopener noreferrer"&gt;PR #3156&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>FinPal - I Built a Finance App You Can Actually Ask Questions To</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Fri, 10 Jul 2026 06:31:27 +0000</pubDate>
      <link>https://dev.to/tejas164321/finpal-i-built-a-finance-app-you-can-actually-ask-questions-to-5dlb</link>
      <guid>https://dev.to/tejas164321/finpal-i-built-a-finance-app-you-can-actually-ask-questions-to-5dlb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;India runs on UPI now - small, constant, invisible payments for chai, autos, groceries, rent splits, all day, every day. What nobody's built is a way to actually &lt;em&gt;ask&lt;/em&gt; your money anything. Not another dashboard you have to squint at - an app you can talk to. &lt;strong&gt;FinPal&lt;/strong&gt; reads your real UPI and bank transaction history (GPay, PhonePe, CSV, PDF exports), auto-categorizes it with a hybrid rules-plus-Gemini engine, and now lets you ask it questions in plain English and get a real answer, grounded in your actual spending.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Why This
&lt;/h3&gt;

&lt;p&gt;I use UPI dozens of times a day, and so does basically everyone I know - that's not an exaggeration in India anymore, it's just how money moves now. NPCI's own numbers make the scale hard to overstate: UPI processed &lt;strong&gt;23.2 billion transactions worth ₹29.9 lakh crore in a single month (May 2026)&lt;/strong&gt; - an average of &lt;strong&gt;737.79 million transactions every day&lt;/strong&gt; - and over &lt;strong&gt;500 million people&lt;/strong&gt; now use it regularly, most of it in small, routine purchases averaging around ₹1,300 a transaction. That's the part that gets lost: when your financial life is made of hundreds of tiny, scattered UPI pings instead of a handful of big bank entries, "where did my money go" stops being a simple question. None of the apps handling those payments were built to answer it - they show you a list, not an explanation. I built FinPal because I wanted the app itself to be able to tell me, the way a person would if you just asked them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Product
&lt;/h3&gt;

&lt;p&gt;FinPal treats AI as infrastructure, not decoration. The hybrid categorization engine keeps things fast and cheap for the transactions it already recognizes, and only calls on Gemini for the genuinely ambiguous ones - the cryptic UPI merchant strings every Indian user has seen and ignored. On top of that foundation, I added &lt;strong&gt;Ask FinPal&lt;/strong&gt;: a natural-language chat layer that answers real questions against your real transaction data, not canned responses.&lt;/p&gt;




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

&lt;p&gt;Instead of scrolling a transaction list trying to piece it together yourself, you just ask. Type something like &lt;em&gt;"How much did I spend on food delivery last month, and is that more than usual?"&lt;/em&gt; or &lt;em&gt;"Can I afford a ₹15,000 trip next month if I keep spending like this?"&lt;/em&gt; - Ask FinPal answers directly, reasoning over your actual categorized transaction history instead of giving generic financial-tips-style advice.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Live
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://finpal.tejasfolio.in/" rel="noopener noreferrer"&gt;https://finpal.tejasfolio.in/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In under a minute:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the dashboard and load a sample statement (or your own CSV/PDF export).&lt;/li&gt;
&lt;li&gt;Watch transactions auto-categorize - rule engine first, Gemini fallback for anything ambiguous.&lt;/li&gt;
&lt;li&gt;Check the income/expense breakdown and SIP/FD simulator update live.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Ask FinPal&lt;/strong&gt; and type a real question about the data you just loaded - no canned demo script, it reasons over what's actually there.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Code
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Tejas164321/FinPal" rel="noopener noreferrer"&gt;https://github.com/Tejas164321/FinPal&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; React 18 + TypeScript on Vite, TailwindCSS 3 with a custom purple-gradient/glassmorphism theme, Radix UI primitives wrapped by shadcn/ui for accessible components, Framer Motion for transitions, Recharts for the dashboard visualizations, React Query for data/caching, React Hook Form + Zod for validated inputs, react-dropzone for uploads, date-fns, sonner for toasts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Node.js + Express 4.18. Multer handles multipart uploads; csv-parser, pdf-parse, and xlsx normalize wildly inconsistent Indian bank/UPI export formats into one internal schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI layer:&lt;/strong&gt; Categorization is rule-based first - heuristics tuned on real UPI merchant-string patterns - escalating to Google Gemini only for what the rules can't confidently place. &lt;strong&gt;Ask FinPal&lt;/strong&gt; builds a compact structured summary of the user's categorized transaction data (not raw dumps - token budget matters) and passes it to Gemini alongside the question, so answers are grounded in the person's actual numbers instead of generic financial advice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the hybrid approach matters:&lt;/strong&gt; most AI finance tools go all-in on the LLM for everything, which is slow, costs more than it needs to, and occasionally gets confidently wrong about things a simple lookup would nail. Splitting the load means the fast path stays fast, and Gemini only gets pulled in where it actually adds value - ambiguous categorization, and now open-ended natural-language questions that no rule engine could ever anticipate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Category: Best Use of Google AI
&lt;/h2&gt;

&lt;p&gt;Gemini isn't bolted on as a chatbot widget - it's doing two distinct jobs in FinPal. First, as a categorization fallback, it only fires for transactions the rule engine can't confidently place, so it's doing targeted, cost-aware work rather than processing every row. Second, in &lt;strong&gt;Ask FinPal&lt;/strong&gt;, it's given a compact, structured summary of the user's own categorized spending - not a raw transaction dump - and asked to reason over it directly, so answers like "is that more than usual?" are grounded in real computed averages rather than generic advice. That combination - cheap-and-fast where possible, Gemini only where genuinely needed - is the core design decision behind the whole app.&lt;/p&gt;




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

&lt;p&gt;I built FinPal because the money moving through my life - and everyone else's around me - got faster and more constant than any of the apps meant to explain it. Hundreds of millions of us are running our financial lives through a payment rail that was built for speed, not clarity. FinPal is my attempt to close that gap: not another list of transactions, but an app you can actually ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NPCI/DFS UPI data, May 2026: 23.2 billion transactions, ₹29.9 lakh crore value - &lt;a href="https://www.ibef.org/news/upi-transactions-soar-to-record-us-312-21-billion-in-may" rel="noopener noreferrer"&gt;https://www.ibef.org/news/upi-transactions-soar-to-record-us-312-21-billion-in-may&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NPCI data via ANI, May 2026 daily average (737.79M/day) - &lt;a href="https://www.aninews.in/news/business/upi-hits-new-high-in-may-2026-with-232-billion-transactions-worth-rs-299-trillion-npci-data-shows20260602155337/" rel="noopener noreferrer"&gt;https://www.aninews.in/news/business/upi-hits-new-high-in-may-2026-with-232-billion-transactions-worth-rs-299-trillion-npci-data-shows20260602155337/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;UPI user base and ticket-size figures - &lt;a href="https://coinlaw.io/upi-statistics/" rel="noopener noreferrer"&gt;https://coinlaw.io/upi-statistics/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>Turing's Mirror - A Game About the Question We Still Haven't Answered</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Sun, 21 Jun 2026 07:08:00 +0000</pubDate>
      <link>https://dev.to/tejas164321/turings-mirror-a-game-about-the-question-we-still-havent-answered-1e3o</link>
      <guid>https://dev.to/tejas164321/turings-mirror-a-game-about-the-question-we-still-havent-answered-1e3o</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://tejas164321.github.io/Turing-s-Mirror/" rel="noopener noreferrer"&gt;Turing's Mirror&lt;/a&gt;&lt;/strong&gt; is a browser game about the one question Alan Turing asked in 1950 that we still haven't answered: can you tell the difference between a human and a machine?&lt;/p&gt;

&lt;p&gt;You read 10 conversations. For each message, you decide: human or AI?&lt;/p&gt;

&lt;p&gt;Sounds simple. Round 1, it is. By Round 5, you'll second-guess everything.&lt;/p&gt;

&lt;p&gt;That's the point.&lt;/p&gt;

&lt;p&gt;🎮 &lt;strong&gt;&lt;a href="https://tejas164321.github.io/Turing-s-Mirror/" rel="noopener noreferrer"&gt;Play it here&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
💻 &lt;strong&gt;&lt;a href="https://github.com/tejas164321/Turing-s-Mirror" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Why Alan Turing, Why Now
&lt;/h2&gt;

&lt;p&gt;June is Turing's birth month. This jam runs through June 21st — the solstice, the longest day, the turning point of the year. The timing felt too right to ignore.&lt;/p&gt;

&lt;p&gt;Turing gave computing its soul. Not the circuits, not the transistors — the &lt;em&gt;question&lt;/em&gt;. In his 1950 paper &lt;em&gt;Computing Machinery and Intelligence&lt;/em&gt;, he didn't ask "can machines think?" He replaced that impossible question with a practical one: can a machine fool a human into thinking it's human?&lt;/p&gt;

&lt;p&gt;He called it the Imitation Game.&lt;/p&gt;

&lt;p&gt;We've been playing it ever since — except now, in 2026, we're the ones being fooled. Regularly. By machines that learned from us.&lt;/p&gt;

&lt;p&gt;I wanted to make people &lt;em&gt;feel&lt;/em&gt; that shift. Not read about it. Feel it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game Design — Light, Dark, and the Solstice
&lt;/h2&gt;

&lt;p&gt;The visual design maps directly onto the jam themes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dark = the machine's world.&lt;/strong&gt; The game opens on a near-black background. Cold, clean, perfect. That's what early AI sounds like: polished in the wrong way, like someone who memorised manners without understanding them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light = human truth.&lt;/strong&gt; As rounds progress and the AI gets more convincingly human, the screen warms. Win — correctly spot the human more than half the time — and the screen floods with light. Solstice light.&lt;/p&gt;

&lt;p&gt;Lose, and you stay in the dark. The machines have crossed the line Turing drew.&lt;/p&gt;

&lt;p&gt;Each round is labelled with a real year from AI history:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Round&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;What it represents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1926&lt;/td&gt;
&lt;td&gt;Before Turing — AI is science fiction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1936&lt;/td&gt;
&lt;td&gt;Turing's first paper on computation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1950&lt;/td&gt;
&lt;td&gt;The Turing Test is published&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1966&lt;/td&gt;
&lt;td&gt;ELIZA, the first chatbot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2026&lt;/td&gt;
&lt;td&gt;Today: AI that passes the test regularly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;By 2026, you're trying to find humanity in text generated in milliseconds. Welcome to the present.&lt;/p&gt;

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


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Tejas164321" rel="noopener noreferrer"&gt;
        Tejas164321
      &lt;/a&gt; / &lt;a href="https://github.com/Tejas164321/Turing-s-Mirror" rel="noopener noreferrer"&gt;
        Turing-s-Mirror
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Turing-s-Mirror&lt;/h1&gt;

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



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Tejas164321/Turing-s-Mirror" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;p&gt;No frameworks. No API keys. No backend. Just HTML, CSS, and vanilla JavaScript — because the simplest formulation of a problem is usually the most revealing one.&lt;/p&gt;

&lt;p&gt;The game data is an array of message pairs — one human, one AI — shuffled randomly so you can't pattern-match by position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;messages&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello! I am doing well today. How may I assist you?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ugh it's so hot today lol, can't even think straight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isAI&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="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1926 — Before the Test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// rounds 2-5 get progressively harder&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hardest part wasn't the code — it was writing the messages. I spent real time rewriting the Round 5 pair. Write an AI response, decide it's too obviously robotic. Make it more human. Decide it's &lt;em&gt;too&lt;/em&gt; human now. Realize: that uncertainty is exactly the problem Turing described.&lt;/p&gt;

&lt;p&gt;The final Round 5 pair:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI: &lt;em&gt;"I think the complicated feeling is the honest one. The clean versions of things usually aren't real."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Human: &lt;em&gt;"that's... actually kind of what I needed to hear. weird how the right words help"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I got this one wrong myself, re-reading it cold a day later.&lt;/p&gt;

&lt;p&gt;The ending closes on Turing's real story, in four lines:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alan Turing (1912–1954). He gave us the test to define intelligence. He was persecuted for who he loved. He was pardoned in 2013 — 59 years too late. The light he switched on for computing never went out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;June is also Pride Month. Turing was gay, and the British government chemically castrated him for it. He died at 41. We live in the world he built; the dignity he was owed never arrived in time. I didn't want to make a game that skips that.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;The Turing Test is not really about AI. It's about us.&lt;/strong&gt; The game teaches you to look for imperfection and emotional messiness as signals of humanity. The scariest moment in playtesting was when someone pointed at a human message and said "that sounds so AI." We've started writing like the models we trained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity is a design choice.&lt;/strong&gt; The entire game is one HTML file and one JS file. No build step. I tried to honor Turing's own instinct: reduce the problem to its essentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Ode to Alan Turing&lt;/strong&gt; — this submission honors Turing through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mechanics&lt;/strong&gt;: the Turing Test isn't referenced, it &lt;em&gt;is&lt;/em&gt; the gameplay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical narrative&lt;/strong&gt;: each round anchors to a real year in AI history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual design&lt;/strong&gt;: light and dark as human truth vs. machine mimicry, tied directly to the solstice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emotional close&lt;/strong&gt;: the full weight of Turing's contribution and the injustice he faced, during his birth month, during Pride Month&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;🎮 &lt;a href="https://tejas164321.github.io/Turing-s-Mirror/" rel="noopener noreferrer"&gt;Play Turing's Mirror&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/tejas164321/Turing-s-Mirror" rel="noopener noreferrer"&gt;View the code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It takes about 5 minutes to play. The Round 5 messages will make you pause.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built solo by Tejas Patil (&lt;a href="https://dev.to/tejas164321"&gt;@tejas164321&lt;/a&gt;) for the DEV June Solstice Game Jam, June 2026.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Tech: Vanilla JS · HTML5 · CSS3 · no dependencies&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Why Hermes Agent Compounds While LangChain Stays Flat — A Deep Architectural Breakdown</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Tue, 26 May 2026 04:49:17 +0000</pubDate>
      <link>https://dev.to/tejas164321/why-hermes-agent-compounds-while-langchain-stays-flat-a-deep-architectural-breakdown-pck</link>
      <guid>https://dev.to/tejas164321/why-hermes-agent-compounds-while-langchain-stays-flat-a-deep-architectural-breakdown-pck</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hermes-agent-2026-05-15"&gt;Hermes Agent Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Every AI agent framework promises to make your agent "smarter." Most of them are lying — not maliciously, but structurally. They build a better loop, a cleaner abstraction, a faster tool-calling interface. And then the 1,000th time your agent runs a task, it performs exactly the same as the first time.&lt;/p&gt;

&lt;p&gt;Hermes Agent does something different. It gets better.&lt;/p&gt;

&lt;p&gt;That's not marketing. It's a specific architectural claim with measurable evidence behind it, real limitations, and a genuine reason it matters to developers who are serious about deploying agents in production. This piece is my attempt to explain it precisely — including the parts that are overstated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three-Tier Framework You Need to Understand
&lt;/h2&gt;

&lt;p&gt;Before Hermes makes sense, you need a mental model of where it sits in the agent landscape. I've found this three-tier classification genuinely clarifying:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1 — Hosted runtimes (OpenAI Agents, Anthropic Agents)&lt;/strong&gt;&lt;br&gt;
These are managed cloud services. Excellent defaults, lowest setup friction, zero self-hosting. The tradeoff: you can't run them on your own infrastructure, your data leaves your perimeter on every call, and the agent's "memory" is whatever the provider chooses to expose through their API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2 — Orchestration libraries (LangChain, CrewAI, AutoGen, LlamaIndex)&lt;/strong&gt;&lt;br&gt;
These are the workhorses of the current AI agent ecosystem. They're flexible, model-agnostic, community-supported, and widely understood. But they're stateless per-run by default. Each task execution starts from the same baseline. The agent has no memory of having done something similar before, no learned shortcuts, no accumulated expertise. You can add memory manually, but it's bolted on — not baked in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3 — Runtime agents with persistent memory, learning, and deployment in the same binary&lt;/strong&gt;&lt;br&gt;
This tier barely existed until 2026. OpenClaw proved the concept. Hermes Agent, released by Nous Research on February 25, 2026, is the first fully MIT-licensed Tier 3 runtime.&lt;/p&gt;

&lt;p&gt;The architectural implication of Tier 3 is significant: the agent's capability is not static. It compounds with use.&lt;/p&gt;


&lt;h2&gt;
  
  
  What "Compounding" Actually Means (With the Math)
&lt;/h2&gt;

&lt;p&gt;Here's the specific mechanism. After any task that involves 5 or more tool calls, Hermes Agent does something Tier 2 frameworks don't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observe the completed workflow&lt;/strong&gt; — what tools were called, in what order, with what parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract it into a skill document&lt;/strong&gt; — a structured Markdown file following the agentskills.io open standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index it into memory&lt;/strong&gt; — now searchable and loadable for future sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply it next time&lt;/strong&gt; — when a similar task appears, the agent loads the relevant skill instead of reasoning from scratch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The performance claim from Nous Research's internal benchmarks: &lt;strong&gt;agents with 20+ self-created skills complete similar tasks 40% faster&lt;/strong&gt; than fresh instances. This is a specific, bounded claim — not "40% better output quality" but "40% less token consumption and wall-clock time to reach equivalent output."&lt;/p&gt;

&lt;p&gt;This distinction matters. The gain is efficiency, not intelligence. The agent isn't smarter — it's not re-doing work it's already learned to do. That's actually a more reliable improvement than "smarter" would be.&lt;/p&gt;
&lt;h3&gt;
  
  
  The honest caveat
&lt;/h3&gt;

&lt;p&gt;Cross-domain generalization doesn't transfer. A skill learned from summarizing GitHub PRs does not help the agent plan a database migration. The skill library is domain-specific. If you're running a general-purpose agent across wildly varied tasks, the compounding effect is weaker than if you're running a focused agent on a narrow, repeated workflow.&lt;/p&gt;

&lt;p&gt;Hermes doesn't claim to solve cross-domain generalization. Nobody has. The compounding advantage is real within a domain, limited across domains.&lt;/p&gt;


&lt;h2&gt;
  
  
  The execute_code Tool: The Feature Everyone Underestimates
&lt;/h2&gt;

&lt;p&gt;Every agent framework has tools. Hermes has one that changes the economics of complex tasks in a way most write-ups miss: &lt;code&gt;execute_code&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Standard agentic tool use looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn 1: Call tool A → get result
Turn 2: Call tool B with result → get result
Turn 3: Call tool C → get result
Turn 4: Call tool D with results from B and C → final output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each turn requires a full model forward pass. On a complex 20-step workflow, that's 20 forward passes, 20 rounds of context building, and the token cost grows with every step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;execute_code&lt;/code&gt; collapses this. The agent writes a Python script that calls other Hermes tools directly via a local RPC bridge. The entire multi-step workflow executes as a single model turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Hermes writes and executes this as one turn
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hermes_tools&lt;/span&gt;

&lt;span class="c1"&gt;# Tool calls via RPC — no new model turns needed
&lt;/span&gt;&lt;span class="n"&gt;repos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hermes_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent frameworks 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;summaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hermes_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hermes_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hermes_tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_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;agent_security_analysis.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model thinks once, plans the entire workflow, executes it in code, and returns the result. For research pipelines, data processing workflows, and multi-step automations, this is dramatically more efficient than the standard turn-by-turn approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture That Makes Self-Hosting Safe
&lt;/h2&gt;

&lt;p&gt;One of the underreported stories around Hermes is its security model, especially compared to OpenClaw which has had multiple CVEs in 2026 (including CVE-2026-25253 for unsafe WebSocket token exposure and documented supply-chain issues).&lt;/p&gt;

&lt;p&gt;Hermes ships with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-only root filesystems&lt;/strong&gt; — the agent can't modify system files even if a malicious tool tries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropped Linux capabilities&lt;/strong&gt; — privilege escalation attack surface is minimized at the kernel level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Namespace isolation&lt;/strong&gt; — each execution environment is isolated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tirith pre-execution scanner&lt;/strong&gt; — prompts are scanned for injection attempts before any tool call executes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just checkbox security. For developers running Hermes agents against internal codebases, company documentation, or databases that contain sensitive data, these defaults matter enormously. The agent getting a malicious instruction through an injected document should not be able to exfiltrate credentials or modify system state. Hermes is designed to fail safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bidirectional MCP Story
&lt;/h2&gt;

&lt;p&gt;Version 0.6.0 of Hermes Agent added something architecturally interesting: it can now act as an MCP server, not just an MCP client.&lt;/p&gt;

&lt;p&gt;Most agent MCP integrations are one-directional — the agent calls external MCP servers to access tools. Hermes flips this. A development team running Claude Code, Cursor, or VS Code with an MCP-compatible AI assistant can &lt;strong&gt;route specific tasks to a locally running Hermes instance&lt;/strong&gt; via MCP.&lt;/p&gt;

&lt;p&gt;The practical implication: you don't have to choose between Hermes and your existing AI stack. You can use Claude for primary reasoning and interface, while delegating long-running autonomous tasks — research pipelines, multi-step file operations, scheduled workflows — to Hermes as a specialized subagent. Hermes runs on your infrastructure, accumulates skills in your domain, and handles the stateful long-horizon work that session-based cloud agents don't do well.&lt;/p&gt;

&lt;p&gt;This bidirectional capability is genuinely new in the open-source agent landscape. It's not in AutoGPT, LangChain, or CrewAI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up Hermes: What the First 30 Minutes Actually Look Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;hermes-agent

&lt;span class="c"&gt;# Configure with your preferred model (works with any OpenAI-compatible endpoint)&lt;/span&gt;
hermes configure &lt;span class="nt"&gt;--model&lt;/span&gt; claude-sonnet-4-20250514 &lt;span class="nt"&gt;--api-key&lt;/span&gt; YOUR_KEY

&lt;span class="c"&gt;# Or use a local model via Ollama&lt;/span&gt;
hermes configure &lt;span class="nt"&gt;--model&lt;/span&gt; ollama/llama3.1 &lt;span class="nt"&gt;--base-url&lt;/span&gt; http://localhost:11434

&lt;span class="c"&gt;# Start the agent&lt;/span&gt;
hermes start

&lt;span class="c"&gt;# Set a persistent goal (works across multiple turns)&lt;/span&gt;
/goal Research and summarize the top 5 open-source agent frameworks released &lt;span class="k"&gt;in &lt;/span&gt;2026, focusing on security models and self-improvement capabilities. Save the result as research/agent_frameworks_2026.md

&lt;span class="c"&gt;# The agent will work on this goal autonomously until a judge model determines it's complete&lt;/span&gt;
&lt;span class="c"&gt;# If it involves 5+ tool calls, it writes a skill document for future reuse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the multi-channel gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# v0.10.0 — single gateway serves all channels&lt;/span&gt;
hermes gateway start &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--telegram-token&lt;/span&gt; YOUR_TOKEN &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slack-webhook&lt;/span&gt; YOUR_WEBHOOK &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--discord-token&lt;/span&gt; YOUR_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  When to Use Hermes vs the Alternatives
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Hermes Agent when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have a focused domain with repeated workflows (research, code review, data processing, content pipelines)&lt;/li&gt;
&lt;li&gt;Self-hosting and data sovereignty are requirements&lt;/li&gt;
&lt;li&gt;You want the agent to become more efficient over weeks and months of use&lt;/li&gt;
&lt;li&gt;You need a long-running autonomous agent, not a session-based one&lt;/li&gt;
&lt;li&gt;You're integrating with an MCP-based stack and want bidirectional compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use LangChain/LangGraph when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need maximum framework flexibility and ecosystem breadth&lt;/li&gt;
&lt;li&gt;You're building a custom, highly specific agent architecture from scratch&lt;/li&gt;
&lt;li&gt;You have existing LangChain infrastructure and migration cost is real&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use OpenClaw when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need immediate access to 5,700+ community skills covering diverse domains&lt;/li&gt;
&lt;li&gt;Rapid time-to-value matters more than long-term optimization&lt;/li&gt;
&lt;li&gt;You've evaluated and accepted the security tradeoffs (or applied the relevant CVE patches)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use OpenAI/Anthropic hosted agents when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup friction matters more than data sovereignty&lt;/li&gt;
&lt;li&gt;You don't need persistent memory or self-improvement&lt;/li&gt;
&lt;li&gt;The task is session-bounded and doesn't recur&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What 95,000 Stars in 10 Weeks Tells You
&lt;/h2&gt;

&lt;p&gt;Hermes Agent crossed 95,600 GitHub stars within roughly ten weeks of launch — a trajectory matched only by a handful of open-source projects in AI history. That growth rate is a signal, not just a vanity metric.&lt;/p&gt;

&lt;p&gt;The developers starring Hermes Agent are not AI hobbyists. They're practitioners who have spent time with LangChain, run OpenClaw in production, and know exactly what gaps they're looking for. When that community signals this strongly about a new framework, the gap being filled is real.&lt;/p&gt;

&lt;p&gt;The gap Hermes fills is the compounding problem. Every serious developer who has built a production AI agent has eventually hit the same wall: the agent is as good on day 300 as it was on day 1. All the prompt engineering, all the tool integrations, all the careful orchestration — none of it makes the agent better at the specific workflows you've taught it to do. It stays flat.&lt;/p&gt;

&lt;p&gt;Hermes Agent is the first MIT-licensed system that is architecturally designed to solve this. Whether it fully delivers over long deployment horizons — and whether the skill compounding holds up across diverse real-world tasks — is still being validated by the community. Three months is not enough time to know.&lt;/p&gt;

&lt;p&gt;But the architecture is right. And the 95,000 stars suggest that developers who have been waiting for this design to exist in open-source form agree.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hermes-agent.nousresearch.com/" rel="noopener noreferrer"&gt;Hermes Agent official site&lt;/a&gt; — full docs, setup guide&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;agentskills.io&lt;/a&gt; — open standard for portable agent skills&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;Hermes Agent GitHub&lt;/a&gt; — MIT licensed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hermes-agent.nousresearch.com/changelog" rel="noopener noreferrer"&gt;v0.10.0 release notes&lt;/a&gt; — 118 bundled skills, 6-channel gateway&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you run Hermes Agent on a real workflow? I'm particularly curious about skill compounding in practice — whether the 40% efficiency claim holds in your domain. Drop your experience in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hermesagentchallenge</category>
      <category>devchallenge</category>
      <category>agents</category>
    </item>
    <item>
      <title>How I Finally Shipped FinPal — Reviving My AI Finance App for 350M UPI Users with GitHub Copilot</title>
      <dc:creator>Tejas Patil</dc:creator>
      <pubDate>Mon, 25 May 2026 06:31:57 +0000</pubDate>
      <link>https://dev.to/tejas164321/how-i-finally-shipped-finpal-reviving-my-ai-finance-app-for-350m-upi-users-with-github-copilot-5dck</link>
      <guid>https://dev.to/tejas164321/how-i-finally-shipped-finpal-reviving-my-ai-finance-app-for-350m-upi-users-with-github-copilot-5dck</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://finpal.tejasfolio.in" rel="noopener noreferrer"&gt;FinPal&lt;/a&gt;&lt;/strong&gt; is an AI-powered personal finance companion built specifically for India's UPI ecosystem — the world's largest real-time payments network with over 350 million active users.&lt;/p&gt;

&lt;p&gt;The problem it solves is real and largely ignored by mainstream finance apps: if you're an Indian user, your transaction history is scattered across Google Pay, PhonePe, Paytm, and your bank's PDF statements. There is no single place to see it all, categorize it, and actually understand where your money goes. Western apps like Mint or YNAB don't understand UPI. Indian banking apps don't talk to each other.&lt;/p&gt;

&lt;p&gt;FinPal does. Upload your GPay history CSV, your bank statement PDF, or your PhonePe export — and it parses, categorizes, and analyzes everything in one dashboard using a hybrid rule-based + Google Gemini AI system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt; React 18 + TypeScript, Vite, TailwindCSS, shadcn/ui, Framer Motion, Recharts, Node.js + Express backend, Google Gemini AI, Radix UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Tejas164321/FinPal" rel="noopener noreferrer"&gt;github.com/Tejas164321/FinPal&lt;/a&gt;&lt;/p&gt;




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

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

&lt;blockquote&gt;
&lt;p&gt;The dashboard shows real financial overview cards, interactive monthly trend charts, category breakdowns, and budget progress tracking. The Transactions page now accepts CSV/PDF uploads, the AI Insights tab runs live Gemini analysis, the Budgets page has full CRUD, and the Investments page has working SIP/FD calculators.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where It Was: The Beautiful Graveyard
&lt;/h3&gt;

&lt;p&gt;Three months ago, I had 137 commits and a project that looked stunning from the outside and was completely hollow on the inside.&lt;/p&gt;

&lt;p&gt;The homepage was polished. The dashboard rendered beautiful charts with mock data. The glassmorphism dark UI was exactly what I'd envisioned. And then there were &lt;strong&gt;four pages&lt;/strong&gt; — Transactions, Budgets, AI Insights, and Investments — that each showed a placeholder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🚧 Coming Soon
This feature is under development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend existed (&lt;code&gt;/server&lt;/code&gt;) with file processing architecture — multer for uploads, csv-parser, pdf-parse, xlsx — all wired up to handle UPI file formats. But it wasn't connected to anything on the frontend. The Gemini AI integration was marked in the README as "architecture prepared." The SIP calculators were listed under "Future Enhancements."&lt;/p&gt;

&lt;p&gt;I'd built the skeleton of something genuinely useful and then got buried in other work. The repo sat there with its four placeholder pages, deployed but empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why It Stalled
&lt;/h3&gt;

&lt;p&gt;The honest reason: I built FinPal in a sprint and the codebase grew faster than my documentation. Coming back to it after two months felt like picking up someone else's project. The component tree was deep. The custom hooks were tightly coupled to mock data. I knew what I wanted to build — I just couldn't remember &lt;em&gt;how&lt;/em&gt; I'd set everything up, and re-reading 137 commits of history to reconstruct the mental model felt like a week of work before writing a single line.&lt;/p&gt;

&lt;p&gt;That's where GitHub Copilot changed everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Changed: The Before → After
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Transactions Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Placeholder page with a "Coming Soon" card&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Drag-and-drop file upload zone (CSV/PDF/Excel), transaction table with pagination, AI/Rule source indicators per row, search and filter, real-time parsing status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Budgets Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Empty shell&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Budget creation form with category selection, progress bars with percentage tracking, over-budget alerts, monthly reset logic, edit/delete functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI Insights Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Listed in README as "Gemini-powered analysis and chat — architecture prepared"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; Live Gemini API integration, spending pattern analysis cards, natural language chat interface, personalized recommendations based on actual uploaded transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Investments Page&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; "SIP calculators and portfolio comparison" listed under Future Enhancements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; SIP calculator with compound interest visualization, FD calculator, maturity comparison chart, editable parameters with real-time chart updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; Server folder existed with file processing modules but no connected API routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After:&lt;/strong&gt; &lt;code&gt;/api/upload&lt;/code&gt; endpoint fully wired, transaction parsing pipeline connected to frontend, Gemini analysis endpoint live&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;This is the part I want to be specific about, because "Copilot helped me write code faster" is a nothing statement. Here's what it actually did.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Re-onboarding to My Own Codebase
&lt;/h3&gt;

&lt;p&gt;The first thing I did after reopening the project was open GitHub Copilot Chat and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Explain the data flow in this codebase from a file upload to displaying transactions on the dashboard. Reference the actual files."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Copilot walked me through &lt;code&gt;server/server.js&lt;/code&gt; → &lt;code&gt;server/processors/&lt;/code&gt; → the React Query hooks in &lt;code&gt;src/hooks/&lt;/code&gt; → the dashboard component. It took 4 minutes to get back the mental model that would have taken me hours of reading commits. That alone was worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generating the Transaction Parser
&lt;/h3&gt;

&lt;p&gt;The most complex piece was the UPI transaction parser. Different banks export CSV in completely different formats. GPay's export has different column headers than HDFC Bank's statement. Copilot generated the initial parsing logic:&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;// Copilot generated this parsing strategy after I described the problem&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detectFormat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phonepe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hdfc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sbi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generic&lt;/span&gt;&lt;span class="dl"&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;headerStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transaction id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upi&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpay&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;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;transaction ref&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phonepe&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;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;narration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chq/ref&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hdfc&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;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;txn id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;headerStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ref no&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sbi&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalizeTransaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Transaction&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;formatMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;gpay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Amount (INR)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transaction Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;phonepe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transaction Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;hdfc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Withdrawal Amt.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Narration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;sbi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Txn Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Debit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;generic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type&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="c1"&gt;// ... normalization logic&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I described the problem — multiple CSV formats, need a normalizer — and Copilot drafted this structure. I modified the field mappings and added error handling. What would have been 2 hours of trial-and-error across real bank exports took 25 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Gemini AI Integration
&lt;/h3&gt;

&lt;p&gt;The AI Insights page needed Gemini to analyze spending patterns and return structured recommendations. I prompted Copilot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Write a function that sends transaction data to Google Gemini and returns categorized spending insights as structured JSON."&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;analyzeWithGemini&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="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&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;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    Analyze these UPI transactions for an Indian user and return a JSON object with:
    - topCategories: array of {category, amount, percentage, trend}
    - savingsOpportunity: string (specific actionable tip)
    - unusualSpending: array of flagged transactions
    - monthlyComparison: {current, previous, change}

    Transactions: &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="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s2"&gt;

    Respond ONLY with valid JSON, no markdown.
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;genAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-pro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&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;text&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 key insight Copilot helped me land: constraining the output to JSON and specifying the exact schema in the prompt. My initial attempts returned markdown-wrapped JSON that broke the parser. Copilot suggested the "Respond ONLY with valid JSON" constraint after I described the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SIP Calculator with Live Chart
&lt;/h3&gt;

&lt;p&gt;The investments page needed a SIP calculator where changing the inputs updates the chart in real time. Copilot wrote the compound interest formula and the Recharts integration in one shot:&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;calculateSIP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;monthlyAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;annualRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;years&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;SIPResult&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;monthlyRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;annualRate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;years&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;months&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;monthlyAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;month&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;returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;monthlyAmount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; 
      &lt;span class="p"&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;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;monthlyRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;month&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="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;monthlyRate&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;monthlyRate&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="nx"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;invested&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;returns&lt;/span&gt;&lt;span class="p"&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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;returns&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;gain&lt;/span&gt;&lt;span class="p"&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;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;returns&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;invested&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;I connected this to a &lt;code&gt;useState&lt;/code&gt; hook for the inputs and wired the output directly to a Recharts &lt;code&gt;AreaChart&lt;/code&gt;. The real-time update as you slide the monthly amount input is one of the most satisfying moments in the whole app.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. What Copilot Couldn't Do
&lt;/h3&gt;

&lt;p&gt;In the spirit of honesty: Copilot didn't understand the UPI-specific domain context without heavy prompting. It generated generic "credit/debit" category logic that missed obvious Indian patterns like "Swiggy" → Food Delivery, "IRCTC" → Travel, "Zepto" → Groceries. I had to build the Indian merchant categorization rules manually — a lookup table of ~80 common UPI merchant patterns. Copilot helped me structure it, but the domain knowledge was mine.&lt;/p&gt;

&lt;p&gt;It also occasionally hallucinated Recharts API props that don't exist. Cross-referencing the Recharts docs was still necessary. Copilot is a fast first draft, not a final implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feature pages working&lt;/td&gt;
&lt;td&gt;2 of 6&lt;/td&gt;
&lt;td&gt;6 of 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code added&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;~2,400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API endpoints connected&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to finish (with Copilot)&lt;/td&gt;
&lt;td&gt;Estimated 3 weeks&lt;/td&gt;
&lt;td&gt;8 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File formats supported&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4 (GPay, PhonePe, HDFC, SBI)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why FinPal Matters
&lt;/h2&gt;

&lt;p&gt;350 million Indians transact on UPI every month. The average urban Indian does 40+ UPI transactions monthly — food delivery, transport, groceries, rent. All of it flows through 3-4 different apps with no unified view.&lt;/p&gt;

&lt;p&gt;FinPal is the tool I wanted and couldn't find. It's not a generic expense tracker. It's built for the specific reality of how Indian digital payments work: the merchant names, the UPI IDs, the bank statement formats, the SIP culture, the FD-first savings mentality.&lt;/p&gt;

&lt;p&gt;GitHub Copilot helped me finish it in 8 days instead of 3 weeks. More importantly, it helped me pick up a codebase I'd half-forgotten and start shipping immediately instead of spending days just getting reoriented.&lt;/p&gt;

&lt;p&gt;The project is live. The placeholder pages are gone. FinPal is finally FinPal.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔗 Live Demo:&lt;/strong&gt; &lt;a href="https://finpal.tejasfolio.in" rel="noopener noreferrer"&gt;finpalai.vercel.app&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;📦 Repo:&lt;/strong&gt; &lt;a href="https://github.com/Tejas164321/FinPal" rel="noopener noreferrer"&gt;github.com/Tejas164321/FinPal&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're an Indian developer building for the UPI ecosystem, I'd love to connect. Drop your thoughts in the comments — especially if you've tried parsing HDFC/SBI statements. The column naming inconsistencies deserve their own post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
