<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Varun Krishnan</title>
    <description>The latest articles on Forem by Varun Krishnan (@not_varunkv).</description>
    <link>https://forem.com/not_varunkv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3608714%2F3d7fbcf6-347c-4905-b0ec-2b7ec91677b4.jpg</url>
      <title>Forem: Varun Krishnan</title>
      <link>https://forem.com/not_varunkv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/not_varunkv"/>
    <language>en</language>
    <item>
      <title>How to Create Product GIFs for Your Landing Page (No Video Editing Required)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Fri, 02 Jan 2026 18:24:08 +0000</pubDate>
      <link>https://forem.com/not_varunkv/how-to-create-product-gifs-for-your-landing-page-no-video-editing-required-5b89</link>
      <guid>https://forem.com/not_varunkv/how-to-create-product-gifs-for-your-landing-page-no-video-editing-required-5b89</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Brochure vs. The Demo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine walking into an electronics store.&lt;br&gt;
You see a sleek new laptop.&lt;br&gt;
Do you just stare at the turned-off screen? No. You touch the trackpad. You open a window. You want to see it work.&lt;/p&gt;

&lt;p&gt;Your landing page is that store.&lt;br&gt;
But most SaaS landing pages are just... brochures.&lt;/p&gt;

&lt;p&gt;They have beautiful, high-res, completely static screenshots. They force the user to read about the features instead of seeing the features.&lt;/p&gt;

&lt;p&gt;And reading is hard work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The 3-Second Rule&lt;/em&gt;&lt;br&gt;
You have roughly 3 seconds to convince a visitor to stay on your site.&lt;/p&gt;

&lt;p&gt;Static Image: The brain has to process the text, look at the UI, map the text to the UI, and simulate the interaction mentally. Cognitive load: High.&lt;br&gt;
GIF/Video: The brain sees movement. It instantly understands "Clicking X does Y." Cognitive load: Low.&lt;br&gt;
Motion is a cheat code for attention.&lt;/p&gt;

&lt;p&gt;Why Not Just Use Video?&lt;br&gt;
"Okay," you say. "I'll just embed a YouTube video."&lt;/p&gt;

&lt;p&gt;You could. But videos are heavy. They require a click to play. They have sound (risky). They take over the experience.&lt;/p&gt;

&lt;p&gt;GIFs are the sweet spot.&lt;/p&gt;

&lt;p&gt;Autoplay: No click needed.&lt;br&gt;
Silent: Safe for work.&lt;br&gt;
Looping: Reinforces the core value proposition.&lt;br&gt;
Lightweight: Loads faster than a 4K video embed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with GIF Creation&lt;/strong&gt;&lt;br&gt;
Creating a polished product GIF used to be painful.&lt;/p&gt;

&lt;p&gt;Record screen (OBS/Loom).&lt;br&gt;
Import to Premiere/After Effects.&lt;br&gt;
Add a device frame overlay.&lt;br&gt;
Mask the video to fit the screen.&lt;br&gt;
Add a background.&lt;br&gt;
Export and pray the file size isn't 50MB.&lt;br&gt;
Most developers (and even designers) don't have time for this. So they settle for static screenshots.&lt;/p&gt;

&lt;p&gt;The Shotframe Solution: GIFs in the Browser&lt;br&gt;
We added GIF support to Shotframe to solve exactly this problem. We wanted the polish of After Effects with the speed of a screenshot tool.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How it works:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Record Your Screen: Use any tool to grab a short clip of your key feature. (Keep it under 5 seconds for best results).&lt;br&gt;
Drop into Shotframe: Upload your video file just like an image.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgolscyr5khbg357gzi0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgolscyr5khbg357gzi0a.png" alt="Drag and Drop the image on Shotframe" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Frame It: Choose a premium device frame (iPhone, MacBook, Browser). Shotframe handles the masking automatically.&lt;/p&gt;

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

&lt;p&gt;Style It: Add a custom gradient background (use BlendIt to match your brand colors!).&lt;br&gt;
Export: Download a highly optimized GIF ready for your landing page.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best Practices for Product GIFs&lt;/strong&gt;&lt;br&gt;
If you're going to use GIFs, do it right:&lt;/p&gt;

&lt;p&gt;Keep it Short: 3-5 seconds max. Focus on ONE interaction.&lt;br&gt;
Start with Action: Don't have 2 seconds of a stillness at the start. Movement should happen immediately.&lt;br&gt;
High Contrast: Make sure the cursor and the button clicks are visible.&lt;br&gt;
Optimization: Don't upload a 20MB GIF to your hero section. Use Shotframe’s export settings to balance quality and size.&lt;br&gt;
Conclusion&lt;br&gt;
Your product is dynamic. Your marketing should be too.&lt;br&gt;
Stop letting your hard work sit still. Make it move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.shotframe.space/" rel="noopener noreferrer"&gt;[Try the Shotframe GIF Maker →]&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>design</category>
    </item>
    <item>
      <title>How to Make Product Mockups That Don't Look Like Templates</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 01 Jan 2026 15:59:59 +0000</pubDate>
      <link>https://forem.com/not_varunkv/how-to-make-product-mockups-that-dont-look-like-templates-f78</link>
      <guid>https://forem.com/not_varunkv/how-to-make-product-mockups-that-dont-look-like-templates-f78</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Stop using generic mockup backgrounds. Learn how to create custom, brand-aligned mockups (and GIFs) directly from your browser. No Figma required.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;strong&gt;The Problem With Modern Mockups&lt;/strong&gt;&lt;br&gt;
Scroll through Dribbble, Product Hunt, or any SaaS landing page. You’ll see the same thing:&lt;/p&gt;

&lt;p&gt;A generic MacBook or iPhone frame.&lt;br&gt;
A "mesh gradient" background (usually purple or blue).&lt;br&gt;
A shadow that looks slightly fake.&lt;br&gt;
It’s the "Startup Starter Pack." And because everyone uses the same templates, no one stands out.&lt;/p&gt;

&lt;p&gt;If you’ve spent weeks building a unique product, wrapping it in a generic mockup kills the vibe instantly.&lt;/p&gt;

&lt;p&gt;Your product deserves better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Context Matters (And Why Templates Fail)&lt;/strong&gt;&lt;br&gt;
A mockup's job isn't just to frame your screenshot. Its job is to extend your brand.&lt;/p&gt;

&lt;p&gt;When you use a random gradient template, you are clashing with your product's actual design.&lt;/p&gt;

&lt;p&gt;The Pro Move: Your background colors should be extracted from the product screenshot itself.&lt;br&gt;
The Result: Perfect color harmony. The background feels like an extension of the UI, not a wrapper.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Method 1: The "Figma Workflow" (The Hard Way)&lt;/em&gt;&lt;br&gt;
You can do this manually, of course.&lt;/p&gt;

&lt;p&gt;Take a screenshot.&lt;br&gt;
Open Figma.&lt;br&gt;
Import screenshot.&lt;br&gt;
Use the Eyedropper tool to pick 3-4 dominant colors.&lt;br&gt;
Create a rectangle layer behind the image.&lt;br&gt;
Apply a linear or radial gradient using those colors.&lt;br&gt;
Add a device frame (search community files for a mockup).&lt;br&gt;
Add drop shadows manually.&lt;br&gt;
Export.&lt;br&gt;
Time: ~15-20 minutes per image.&lt;br&gt;
Result: Good, but slow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Method 2: The Shotframe Workflow (The Fast Way)&lt;/em&gt;&lt;br&gt;
We built Shotframe to automate the "Pro Move."&lt;/p&gt;

&lt;p&gt;Step 1: Upload Your Screenshot&lt;br&gt;
Drop your image into the browser. No login needed.&lt;/p&gt;

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

&lt;p&gt;Step 2: Auto-Match the Background&lt;br&gt;
Instead of picking a random color, use a tool like BlendIt to generate a gradient directly from your screenshot. Upload that gradient as your background in Shotframe.&lt;/p&gt;

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

&lt;p&gt;Now your background matches your UI perfectly.&lt;/p&gt;

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

&lt;p&gt;Step 3: Choose Your Device&lt;br&gt;
Select from premium device frames (Safari Dark, iPhone 15, Chrome Light). It wraps your screenshot automatically with perfect shadows.&lt;/p&gt;

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

&lt;p&gt;Step 4: Go Beyond Static (New Feature)&lt;br&gt;
Static images are fine. Motion is better.&lt;/p&gt;

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

&lt;p&gt;Shotframe now lets you export as GIF.&lt;/p&gt;

&lt;p&gt;Show a scrolling capture.&lt;br&gt;
Show a UI interaction.&lt;br&gt;
Show a workflow.&lt;br&gt;
This turns a "picture of an app" into a "demo of an app."&lt;/p&gt;

&lt;p&gt;Why GIFs Convert Better Than Images&lt;br&gt;
On a landing page, you have about 3 seconds to explain what your tool does.&lt;/p&gt;

&lt;p&gt;Screenshot: The user has to read the UI to understand it.&lt;br&gt;
GIF: The user sees the UI working.&lt;br&gt;
It builds trust. It proves the software is real. It catches the eye.&lt;/p&gt;

&lt;p&gt;Creating Your First Custom Mockup&lt;br&gt;
Go to Shotframe.xyz.&lt;br&gt;
Drop your image.&lt;br&gt;
Tweak the background (or upload your custom BlendIt gradient).&lt;br&gt;
Hit Export.&lt;br&gt;
Stop letting generic mockups lower your product's value. Make it look as premium as the code behind it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.shotframe.space/" rel="noopener noreferrer"&gt;[Create a Free Mockup Now →]&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Create a Gradient from Any Photo (Step-by-Step)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Wed, 31 Dec 2025 19:40:58 +0000</pubDate>
      <link>https://forem.com/not_varunkv/how-to-create-a-gradient-from-any-photo-step-by-step-4379</link>
      <guid>https://forem.com/not_varunkv/how-to-create-a-gradient-from-any-photo-step-by-step-4379</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Learn how to extract colors from any photo and turn them into beautiful gradients. Stop guessing hex codes—let your photos pick the perfect palette.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Stop Guessing Hex Codes. Let Your Photos Decide.&lt;/strong&gt;&lt;br&gt;
Every designer has been here:&lt;/p&gt;

&lt;p&gt;Open a gradient generator. Pick random colors. Hate it. Repeat 47 times. Settle for something "fine."&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;p&gt;Instead of inventing colors from scratch, extract them from photos you already love. Sunsets, coffee cups, product shots—they all contain palettes that already work together.&lt;/p&gt;

&lt;p&gt;Why? Because nature figured out color harmony millions of years ago. We're just borrowing it.&lt;/p&gt;

&lt;p&gt;This guide shows you how to turn any photo into a usable gradient in under 60 seconds.&lt;/p&gt;




&lt;p&gt;Why Photos Make Better Palettes&lt;br&gt;
Most gradient generators give you "trending" palettes. The problem? Everyone uses the same ones.&lt;/p&gt;

&lt;p&gt;That's why every SaaS landing page has the same purple-blue mesh gradient.&lt;/p&gt;

&lt;p&gt;Photos solve this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Colors Already Harmonize&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Colors in a photo exist together naturally. A sunset has warm oranges, soft pinks, and deep purples—all in perfect balance. You didn't pick them. Light did.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Nature Is Never Wrong"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An old design principle. Flowers, landscapes, street scenes—colors that appear together in real life just work. No color theory degree required.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unique to Your Brand&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you pull colors from YOUR product shot or YOUR office photo, the gradient is instantly tied to your brand. Not a trending palette. Yours.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Step-by-Step: Photo to Gradient&lt;/em&gt;&lt;br&gt;
Here's the simple workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Choose Your Photo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick something with colors you like:&lt;/p&gt;

&lt;p&gt;Product screenshot (brand-aligned)&lt;br&gt;
Landscape (natural, calming)&lt;br&gt;
Street photography (urban, moody)&lt;br&gt;
Even a coffee cup on your desk&lt;br&gt;
The best photos have a clear color mood. Avoid images with too many competing colors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9uzcf8yxlhjznb8wfo2b.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.amazonaws.com%2Fuploads%2Farticles%2F9uzcf8yxlhjznb8wfo2b.jpg" alt="Scene image got from https://walle.theblank.club/" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Upload to a Color Extraction Tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop your image into BlendIt (or similar tool).&lt;/p&gt;

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

&lt;p&gt;It automatically detects the dominant colors and arranges them into a gradient.&lt;/p&gt;

&lt;p&gt;No eyedropper. No manual picking. Done in seconds.&lt;/p&gt;

&lt;p&gt;Step 3: Adjust If Needed&lt;/p&gt;

&lt;p&gt;Sometimes the auto-pick isn't perfect. Most tools let you:&lt;/p&gt;

&lt;p&gt;Swap colors&lt;br&gt;
Adjust positions&lt;br&gt;
Change gradient style (mesh, linear, radial)&lt;br&gt;
Tweak until it feels right.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkxe8he8gq9m0nsxqbsj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwkxe8he8gq9m0nsxqbsj.png" alt="Tweak the gradiency as you wish" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 4: Add Texture (Optional)&lt;/p&gt;

&lt;p&gt;Flat gradients can look sterile. Adding subtle grain or noise gives depth and a premium feel.&lt;/p&gt;

&lt;p&gt;This is popular in modern SaaS and Web3 design.&lt;/p&gt;

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

&lt;p&gt;Step 5: Export&lt;/p&gt;

&lt;p&gt;Download as PNG for landing pages and social media.&lt;/p&gt;

&lt;p&gt;Or copy the CSS for direct use in code.&lt;/p&gt;

&lt;p&gt;Done. 60 seconds. No guessing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Use Cases&lt;/strong&gt;&lt;br&gt;
Where can you use photo-extracted gradients?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Landing Page Backgrounds&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hero sections need something more interesting than solid color but less busy than photos. Gradients from your product colors = perfect middle ground.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Social Media Graphics&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Instagram posts, LinkedIn carousels, Twitter headers. Matching gradients make your content look cohesive.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Presentation Slides&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pitch decks and internal presentations. Stand out from the default PowerPoint templates.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Device Wallpapers&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Personal touch for your phone or desktop.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mockup Backgrounds&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When showcasing your product in mockups, use a gradient from the product itself. Everything matches automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tips for Best Results&lt;/strong&gt;&lt;br&gt;
Use Photos with Clear Color Themes&lt;/p&gt;

&lt;p&gt;A photo of a sunset works great. A photo of a busy street market? Too many colors competing.&lt;/p&gt;

&lt;p&gt;Product Shots = Brand Alignment&lt;/p&gt;

&lt;p&gt;Pull colors from your actual product screenshot. Your gradient will match your brand without any extra effort.&lt;/p&gt;

&lt;p&gt;Landscapes = Natural Vibes&lt;/p&gt;

&lt;p&gt;Mountains, oceans, forests. These give calm, organic palettes.&lt;/p&gt;

&lt;p&gt;Experiment with Unexpected Sources&lt;/p&gt;

&lt;p&gt;Coffee. Book covers. Album art. Some of the best gradients come from random everyday photos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lazy Designer's Secret&lt;/strong&gt;&lt;br&gt;
Here's the truth:&lt;/p&gt;

&lt;p&gt;Great color choices aren't about talent. They're about sources.&lt;/p&gt;

&lt;p&gt;Designers who seem to have "good taste" often just steal from better sources—nature, photography, real life.&lt;/p&gt;

&lt;p&gt;Now you can too.&lt;/p&gt;

&lt;p&gt;Stop staring at color pickers. Stop cycling through trending palettes.&lt;/p&gt;

&lt;p&gt;Take a photo. Extract the colors. Move on with your life.&lt;/p&gt;

&lt;p&gt;Try It Yourself&lt;br&gt;
BlendIt makes this workflow instant:&lt;/p&gt;

&lt;p&gt;Drop any photo&lt;br&gt;
Colors extracted automatically&lt;br&gt;
Gradient generated with grain texture&lt;br&gt;
Export PNG or copy CSS&lt;br&gt;
Free to use. No account needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.blendit.space/" rel="noopener noreferrer"&gt;[Try BlendIt →]&lt;/a&gt;&lt;/p&gt;

</description>
      <category>design</category>
      <category>css</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The 48-Hour Button: Fighting Supabase, Google Auth, and "Scope Creep"</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Fri, 28 Nov 2025 20:19:28 +0000</pubDate>
      <link>https://forem.com/not_varunkv/the-48-hour-button-fighting-supabase-google-auth-and-scope-creep-1e7n</link>
      <guid>https://forem.com/not_varunkv/the-48-hour-button-fighting-supabase-google-auth-and-scope-creep-1e7n</guid>
      <description>&lt;p&gt;I just spent &lt;em&gt;48 hours&lt;/em&gt; building a feature the user will never notice.&lt;br&gt;
It wasn't a complex algorithm. It wasn't a 3D animation.&lt;/p&gt;

&lt;p&gt;It was a &lt;strong&gt;"Sign in with Google"&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;For the past two days, the Direct Google Sheets integration—my most requested feature—was dead on arrival.&lt;/p&gt;

&lt;p&gt;Here is the technical breakdown of why "simple" integrations are rarely simple, and how I solved the Supabase Scope conflict.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Invisible Villain:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Supabase vs. Google Scopes&lt;br&gt;
The goal was simple:&lt;/p&gt;

&lt;p&gt;User logs into SpeakSheet.&lt;br&gt;
User clicks "Export to Google Sheets."&lt;br&gt;
My app writes the data to their Drive.&lt;/p&gt;

&lt;p&gt;I am using Supabase for authentication. It handles the login flow perfectly. But when I tried to push data to the Google Sheets API, I got the same error loop:&lt;br&gt;
403: Insufficient Permission&lt;/p&gt;

&lt;p&gt;I debugged for hours. I checked the Google Cloud Console. I checked the API keys. Everything looked green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Realization&lt;/strong&gt;&lt;br&gt;
The issue wasn't the API; it was the Auth Token.&lt;br&gt;
By default, Supabase Auth requests basic scopes: email and profile. It does not request spreadsheets (write access).&lt;/p&gt;

&lt;p&gt;The token I held verified who the user was, but it didn't have permission to touch their files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: Decoupling the Auth&lt;/strong&gt;&lt;br&gt;
I tried to force the scope into the initial login, but it created a messy user experience (asking for full Drive access just to sign up?).&lt;/p&gt;

&lt;p&gt;I realized I had to change the workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The New Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Login: User signs in via Supabase (Standard Email/Profile scope).&lt;br&gt;
App Access: User builds their prompt and generates the schema.&lt;br&gt;
Integration: When they click "Export to Google Sheets," I trigger a secondary re-authentication.&lt;br&gt;
This specific step asks only for the Sheets permission. It separates "Identity" from "Capability." It’s cleaner, safer, and it finally works.&lt;/p&gt;

&lt;p&gt;From "Text" to "Logic"&lt;br&gt;
Once the gate was open, I had to ensure the payload was correct.&lt;/p&gt;

&lt;p&gt;This is the difference between ChatGPT and SpeakSheet.&lt;br&gt;
If you ask a standard LLM for a "Budget Tracker," it gives you a text explanation.&lt;br&gt;
I need to give you working cells.&lt;/p&gt;

&lt;p&gt;My system prompt enforces a strict JSON schema. It flags specific fields as Formula:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;{"formulaLabel":"Sum", "formula":"("=SUM(C:C))"}&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Before: The API treated formulas as text strings.&lt;br&gt;
Now: The API recognizes the = operator and parses logic, including complex IF statements.&lt;/p&gt;

&lt;p&gt;What’s Next? (The boring part)&lt;br&gt;
The engine is running. The integration is live.&lt;br&gt;
Now, I have to build the toll booth.&lt;/p&gt;

&lt;p&gt;I am implementing the Pricing page next.&lt;br&gt;
I have chosen LemonSqueezy as my merchant of record.&lt;/p&gt;

&lt;p&gt;Why? Because Stripe is currently not onboarding new businesses in India.&lt;br&gt;
The Upside: LemonSqueezy handles the tax compliance, which lets me focus on the code.&lt;/p&gt;

&lt;p&gt;The MVP is 90% complete. &lt;br&gt;
Next update: The Mobile Responsiveness fix.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>nextjs</category>
      <category>saas</category>
    </item>
    <item>
      <title>"Is this just a wrapper?" (How a Reddit Comment Changed My Roadmap)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Sun, 23 Nov 2025 10:46:13 +0000</pubDate>
      <link>https://forem.com/not_varunkv/is-this-just-a-wrapper-how-a-reddit-comment-changed-my-roadmap-1pen</link>
      <guid>https://forem.com/not_varunkv/is-this-just-a-wrapper-how-a-reddit-comment-changed-my-roadmap-1pen</guid>
      <description>&lt;p&gt;I launched the MVP of SpeakSheet on Reddit this week.&lt;/p&gt;

&lt;p&gt;The concept is simple: You type a prompt, and my app generates a structured Excel file using Gemini.&lt;/p&gt;

&lt;p&gt;The post got 2,400 views. Most feedback was standard. But one comment stopped me cold.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Is it not just a system prompt for Gemini? It is not a product... users still have to prompt. You could chat with Gemini on Google Sheets all day long and it is free.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My initial reaction was defensive.&lt;br&gt;
I wrote the NextJS frontend. I set up the Supabase backend. I wrote the complex system prompts that enforce schema validation so the JSON doesn't break. Of course it's a product!&lt;/p&gt;

&lt;p&gt;But after the sting faded, I realized he was right.&lt;br&gt;
And he was also wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Wrapper" Fallacy&lt;/strong&gt;&lt;br&gt;
Developers (including me) obsess over the tech stack. We think "value" means proprietary algorithms.&lt;/p&gt;

&lt;p&gt;But to a user who doesn't know what an API key is, raw technology is useless.&lt;/p&gt;

&lt;p&gt;They don't know how to write a system prompt to enforce column structures.&lt;br&gt;
They don't know how to parse a file upload to extract context.&lt;br&gt;
They don't want to "chat" with a spreadsheet; they want the spreadsheet to exist.&lt;br&gt;
My value isn't the AI model. My value is the packaging.&lt;/p&gt;

&lt;p&gt;SpeakSheet wraps the chaos of an LLM into a predictable, one-click interface. That is the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pivot: Listening to the Haters&lt;/strong&gt;&lt;br&gt;
The most interesting part of the comment was this line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You could chat with Gemini on Google Sheets all day long."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I realized I had missed a massive use case.&lt;br&gt;
Right now, SpeakSheet generates a downloadable .xlsx file. But many users live in the browser. They don't want to download, open, and upload.&lt;/p&gt;

&lt;p&gt;Because of that "hater," I am shifting my roadmap.&lt;/p&gt;

&lt;p&gt;I am now researching Google Sheets Integration via OAuth 2.0.&lt;br&gt;
The goal: A user types a prompt in SpeakSheet, and it pushes the data directly into their live Google Sheet. No downloads. No file management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building the Integration (The Plan)&lt;/strong&gt;&lt;br&gt;
I haven't built this yet. I am currently studying the Google Workspace API documentation.&lt;br&gt;
The plan is:&lt;/p&gt;

&lt;p&gt;Authenticate the user via OAuth 2.0.&lt;br&gt;
Generate the schema via Gemini (as I do now).&lt;br&gt;
Push the structured data into a new Sheet via the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
If you are building a Micro-SaaS, do not fear the "Wrapper" accusation.&lt;br&gt;
Power users will always say they can do it themselves. They are not your customers.&lt;/p&gt;

&lt;p&gt;Your customers are the people who gladly pay to skip the learning curve.&lt;/p&gt;

&lt;p&gt;And sometimes, your harshest critics give you your best feature ideas.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>devjournal</category>
      <category>discuss</category>
      <category>ai</category>
    </item>
    <item>
      <title>My Code Worked. Excel’s "Protected View" Killed It.</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Sat, 22 Nov 2025 12:07:46 +0000</pubDate>
      <link>https://forem.com/not_varunkv/my-code-worked-excels-protected-view-killed-it-d58</link>
      <guid>https://forem.com/not_varunkv/my-code-worked-excels-protected-view-killed-it-d58</guid>
      <description>&lt;p&gt;I am building a Micro-SaaS called SpeakSheet. The premise is simple: You speak (or type) a prompt, and it generates a structured Excel file.&lt;/p&gt;

&lt;p&gt;The stack is solid: NextJS, Tailwind, Supabase, and Gemini on the backend.&lt;/p&gt;

&lt;p&gt;This week, I tackled the hardest part of the MVP: Formulas.&lt;/p&gt;

&lt;p&gt;Teaching an LLM to understand "Profit Margin" is tricky. It knows the math, but it doesn't know the context. After hours of tweaking the JSON schema and refining the prompt, I finally got a green light in the console.&lt;/p&gt;

&lt;p&gt;The logic was perfect. The schema was validated. I downloaded the generated file to test it.&lt;/p&gt;

&lt;p&gt;Profit Margin: 0.&lt;/p&gt;

&lt;p&gt;I stared at the screen. I felt that specific mix of confusion and anger that only developers know. I checked the backend logs—the calculation was correct. I checked the cell data—the formula was there.&lt;/p&gt;

&lt;p&gt;But the cell displayed 0.&lt;/p&gt;

&lt;p&gt;I spent an hour debugging a bug that didn't exist.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Invisible Villain&lt;/strong&gt;&lt;br&gt;
Then, I looked at the top of the Excel window. A thin yellow bar:&lt;/p&gt;

&lt;p&gt;Protected View: Be careful—files from the Internet can contain viruses.&lt;/p&gt;

&lt;p&gt;Because my software generated the file programmatically, Excel didn't trust it.&lt;/p&gt;

&lt;p&gt;It blocked the execution.&lt;br&gt;
It suppressed the formula results.&lt;br&gt;
It made my product look broken.&lt;br&gt;
This is the reality of building Micro-SaaS. You spend 20% of your time writing logic in NextJS, and 80% of your time fighting edge cases in the tools your customers actually use.&lt;/p&gt;

&lt;p&gt;I realized that for a user with low Excel literacy (my target audience), this is a dealbreaker. They won't click "Enable Editing." They will just churn.&lt;/p&gt;

&lt;p&gt;I had to refactor the user journey to account for a security feature I have no control over.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo7g1u351nupvwvlrfa4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdo7g1u351nupvwvlrfa4.png" alt="Landing Page of SpeakSheet - An excel helper better that excel." width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;The Design Pivot &lt;em&gt;(feat. Gemini 3.0)&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Once the logic was fixed, I looked at the UI. It was functional, but it looked like a Bootstrap template from 2015.&lt;/p&gt;

&lt;p&gt;I am a developer, not a designer. Usually, this is where I stall.&lt;/p&gt;

&lt;p&gt;I decided to test Gemini 3.0 Flash. I gave it a simple instruction: Design a modern, clean landing page for a SaaS that converts text to Excel.&lt;/p&gt;

&lt;p&gt;I expected the usual generic AI slop.&lt;br&gt;
Instead, it one-shot a layout that actually looked... premium. It understood whitespace. It utilized the ShadCN components correctly.&lt;/p&gt;

&lt;p&gt;It saved me two days of CSS wrestling.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Lesson&lt;/strong&gt;&lt;br&gt;
Building SpeakSheet has taught me that code is only half the product.&lt;/p&gt;

&lt;p&gt;Your logic can be perfect, but if Excel's UI hides it, you failed.&lt;br&gt;
Your backend can be robust, but if the frontend looks cheap, you failed.&lt;br&gt;
I am building this in public to show the messy reality of shipping a product.&lt;/p&gt;




&lt;p&gt;Follow me here for the next update.&lt;br&gt;
&lt;a href="https://x.com/NotVarunKV" rel="noopener noreferrer"&gt;@NotVarunKV&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch Demo of the working here:&lt;br&gt;
&lt;a href="https://x.com/NotVarunKV/status/1992189268559864189?s=20" rel="noopener noreferrer"&gt;Demo Working&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>When Users Ask for 'Profit Margin' and You Realize Formulas Are Harder Than You Thought</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 20 Nov 2025 11:13:36 +0000</pubDate>
      <link>https://forem.com/not_varunkv/when-users-ask-for-profit-margin-and-you-realize-formulas-are-harder-than-you-thought-4eg8</link>
      <guid>https://forem.com/not_varunkv/when-users-ask-for-profit-margin-and-you-realize-formulas-are-harder-than-you-thought-4eg8</guid>
      <description>&lt;p&gt;Formula support is working in SpeakSheet.&lt;/p&gt;

&lt;p&gt;But getting here? That was a journey I didn't expect.&lt;/p&gt;

&lt;p&gt;Here's what happened when I tried to teach AI the difference between "calculate total" and "calculate profit margin."&lt;/p&gt;




&lt;h2&gt;
  
  
  The Request That Started It All
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My Brain&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is cool, but can it add formulas? Like, I want to track revenue and cost, and have it automatically calculate profit margin."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Me:&lt;/strong&gt; "Sure, how hard can that be?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model:&lt;/strong&gt; &lt;em&gt;It was, in fact, harder than that.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Naive Approach (That Failed)
&lt;/h2&gt;

&lt;p&gt;My first thought: "Just tell the AI to add formulas."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt to Gemini:&lt;/strong&gt;&lt;br&gt;
User wants: "Track sales with revenue and cost. Calculate profit margin."&lt;/p&gt;

&lt;p&gt;Generate an Excel file with a profit margin formula.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem: AI Doesn't Understand Context
&lt;/h2&gt;

&lt;p&gt;I realized the issue:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini is great at understanding WHAT the user wants:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ "Calculate profit margin" → User wants ((Revenue - Cost) / Revenue) * 100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;But terrible at the specifics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Should it be a decimal or percentage?&lt;/li&gt;
&lt;li&gt;❌ Should it use cell references (B2) or column names (Revenue)?&lt;/li&gt;
&lt;li&gt;❌ Should it calculate the value or generate a formula?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Every time I asked, I got different outputs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not good enough for production.&lt;/p&gt;


&lt;h2&gt;
  
  
  Solution (That Actually Works): Schema-Based Formula Generation
&lt;/h2&gt;

&lt;p&gt;Here's what I changed:&lt;/p&gt;
&lt;h3&gt;
  
  
  The New Flow:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: AI analyzes user's file + prompt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When user uploads an existing Excel file and adds a prompt like:&lt;br&gt;
I send to Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"userPrompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Calculate profit margin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileSchema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rowCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: AI returns structured schema (not formulas)&lt;br&gt;
Gemini responds with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"calculation_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profit_margin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"columns_needed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Revenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cost"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_column"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profit Margin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"percentage"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: I generate the formula using system prompt template&lt;/p&gt;

&lt;p&gt;This is where it gets interesting.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The Problem I Didn't Expect: *&lt;/em&gt;&lt;br&gt;
Excel's "Enable Editing" Button&lt;br&gt;
Formulas were generating perfectly.&lt;/p&gt;

&lt;p&gt;But when users opened the files:&lt;br&gt;
"The profit margin shows 0 instead of 40%"&lt;/p&gt;

&lt;p&gt;I tested on my machine: Worked fine.&lt;br&gt;
Sent to beta tester: Shows 0.&lt;br&gt;
What??&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Investigation&lt;/strong&gt;&lt;br&gt;
After an hour of debugging, I asked the beta tester to screen share.&lt;/p&gt;

&lt;p&gt;What I saw:&lt;br&gt;
They open Excel file&lt;br&gt;
Yellow banner at top: "PROTECTED VIEW"&lt;br&gt;
Formula cells show: 0&lt;br&gt;
They click "Enable Editing"&lt;br&gt;
Formula cells suddenly show: 40%&lt;br&gt;
Oh no.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Root Cause:&lt;/em&gt;&lt;br&gt;
Excel Protected View&lt;br&gt;
Excel opens files from "untrusted sources" (downloads, programmatically-generated files) in Protected View.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In Protected View:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Formulas calculate in the background ✅&lt;br&gt;
But Excel shows 0 instead of the calculated value ❌&lt;br&gt;
Users must click "Enable Editing" to see results ❌&lt;br&gt;
This made my AI-generated files look broken.&lt;/p&gt;

&lt;p&gt;Users thought: "The formula doesn't work."&lt;/p&gt;

&lt;p&gt;Reality: "The formula works, Excel is hiding the value."&lt;/p&gt;

&lt;p&gt;But Wait, There's More: &lt;br&gt;
&lt;strong&gt;&lt;em&gt;Complete UI Overhaul&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
While fixing formulas, I realized the UI was... not good.&lt;/p&gt;

&lt;p&gt;The old UI:&lt;br&gt;
Generic input box&lt;br&gt;
Basic "Generate" button&lt;br&gt;
No feedback during processing&lt;br&gt;
Looked like a 2010 Bootstrap template&lt;/p&gt;

&lt;p&gt;The new UI&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjaz03xp8fsjo8ire3qy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjaz03xp8fsjo8ire3qy.png" alt="The interface for speaksheet generating excel files" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;_The Rebuild: _&lt;/strong&gt;&lt;br&gt;
Using Gemini 3.0 Flash to Design the New UI&lt;br&gt;
I did something unconventional:&lt;/p&gt;

&lt;p&gt;I asked Gemini 3.0 Flash to design the UI for me.&lt;/p&gt;

&lt;p&gt;Not just generate code. Actually DESIGN the user experience.&lt;/p&gt;

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

&lt;p&gt;How's the new look?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>nextjs</category>
      <category>startup</category>
    </item>
    <item>
      <title>Day 6: Why Excel Shows '0' for My AI-Generated Formulas (And How I Fixed It)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Tue, 18 Nov 2025 20:56:31 +0000</pubDate>
      <link>https://forem.com/not_varunkv/day-6-why-excel-shows-0-for-my-ai-generated-formulas-and-how-i-fixed-it-3mfi</link>
      <guid>https://forem.com/not_varunkv/day-6-why-excel-shows-0-for-my-ai-generated-formulas-and-how-i-fixed-it-3mfi</guid>
      <description>&lt;p&gt;Yes I did nothing on Day 5. Had my exams, it hit me with anxiety. So was studying late night.&lt;/p&gt;

&lt;p&gt;Back to the point:&lt;br&gt;
I added formula support to SpeakSheet today (my AI Excel generator).&lt;/p&gt;

&lt;p&gt;Formulas generated perfectly. But when users opened the files, they saw &lt;code&gt;0&lt;/code&gt; instead of calculated values.&lt;/p&gt;

&lt;p&gt;Took me an hour to realize: The formulas WERE working. Excel was just hiding the results.&lt;/p&gt;

&lt;p&gt;Here's what happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Feature: AI-Generated Formulas
&lt;/h2&gt;

&lt;p&gt;SpeakSheet now supports formulas in prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User types:&lt;/strong&gt;&lt;br&gt;
Track employees: Name, Salary. Calculate average salary&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI generates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excel file with Name and Salary columns&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=AVERAGE(B2:B10)&lt;/code&gt; in cell B11&lt;/li&gt;
&lt;li&gt;Formula should display calculated average&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The Bug Report&lt;br&gt;
When opening the file the data has:&lt;/p&gt;

&lt;p&gt;A4: Average:&lt;br&gt;
B4: 0&lt;br&gt;
Not the calculated value. Just: 0&lt;/p&gt;

&lt;h2&gt;
  
  
  My reaction: "That makes no sense. The formula is =AVERAGE(B2:B4). It should show 55000."
&lt;/h2&gt;

&lt;p&gt;The Root Cause: Protected View Display Behavior&lt;br&gt;
Excel's Protected View doesn't just disable editing. It also hides calculated formula values.&lt;/p&gt;

&lt;p&gt;What's actually happening:&lt;/p&gt;

&lt;p&gt;When file opens in Protected View:&lt;/p&gt;

&lt;p&gt;Cell B4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formula: =AVERAGE(B2:B4) ✅&lt;/li&gt;
&lt;li&gt;Calculated value: 55000 ✅&lt;/li&gt;
&lt;li&gt;Displayed value: 0 ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After clicking "Enable Editing":&lt;/p&gt;

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

&lt;p&gt;Cell B4:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Formula: =AVERAGE(B2:B4) ✅&lt;/li&gt;
&lt;li&gt;Calculated value: 55000 ✅&lt;/li&gt;
&lt;li&gt;Displayed value: 55000 ✅
The formula WAS calculating correctly. Excel just wasn't showing the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why Excel Does This&lt;br&gt;
Protected View is a security feature for files from "untrusted sources":&lt;/p&gt;

&lt;p&gt;Email attachments&lt;br&gt;
Downloads from internet&lt;br&gt;
Programmatically-generated files&lt;br&gt;
Security measures:&lt;/p&gt;

&lt;p&gt;❌ Editing disabled&lt;br&gt;
❌ Macros blocked&lt;br&gt;
❌ External connections blocked&lt;br&gt;
❌ Formula values hidden (shows 0 instead)&lt;br&gt;
Why hide formula values?&lt;/p&gt;

&lt;p&gt;Malicious files could use formulas to:&lt;/p&gt;

&lt;p&gt;Display misleading information&lt;br&gt;
Exploit calculation bugs&lt;br&gt;
Trigger unintended behavior&lt;br&gt;
By showing 0 instead of calculated values, Excel forces users to consciously "Enable Editing" before trusting the displayed data.&lt;/p&gt;

&lt;p&gt;Reasonable security. Confusing UX.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>saas</category>
      <category>learning</category>
    </item>
    <item>
      <title>Day 4: Why I Spent More Time on UI Than Features</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Mon, 17 Nov 2025 22:27:14 +0000</pubDate>
      <link>https://forem.com/not_varunkv/day-4-why-i-spent-more-time-on-ui-than-features-4mo3</link>
      <guid>https://forem.com/not_varunkv/day-4-why-i-spent-more-time-on-ui-than-features-4mo3</guid>
      <description>&lt;p&gt;Today I completed both core features of SpeakSheet (AI Excel generator).&lt;/p&gt;

&lt;p&gt;Then I spent twice as long rebuilding the UI.&lt;/p&gt;

&lt;p&gt;Here's why that mattered more than I expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Shipped Today
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Feature 1: Generate Excel from Text ✅
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;User flow:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type: "Client orders: Name, Product, Quantity, Price, Date"&lt;/li&gt;
&lt;li&gt;Click "Generate"&lt;/li&gt;
&lt;li&gt;Download formatted Excel file (~10 seconds)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Gemini API → Schema generation → Excel creation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Complete&lt;/p&gt;




&lt;h3&gt;
  
  
  Feature 2: Upload + Intelligent Data Insertion ✅
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;User flow:&lt;/strong&gt;&lt;br&gt;
What it does:&lt;br&gt;
→ User uploads messy Excel file&lt;br&gt;
→ Types: "Client orders: Name, Product, Quantity, Price, Date"&lt;br&gt;
→ AI finds the right table and inserts into the new updated file&lt;br&gt;
→ Download the file&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend:&lt;/strong&gt; File parsing → AI matching → Validation → Insertion&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Working, needs more edge case testing&lt;/p&gt;


&lt;h2&gt;
  
  
  The Problem I Didn't Expect
&lt;/h2&gt;

&lt;p&gt;Both features worked perfectly on the backend.&lt;/p&gt;

&lt;p&gt;But when I tested with 3 friends yesterday:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every single one&lt;/strong&gt; thought it was broken.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI gave zero feedback.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User clicks "Generate" → 10 seconds of silence → File appears&lt;/p&gt;

&lt;p&gt;During those 10 seconds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI is processing ✅&lt;/li&gt;
&lt;li&gt;Schema is generating ✅&lt;/li&gt;
&lt;li&gt;Excel file is being created ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But users see: Nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; "Is this working? Should I click again?"&lt;/p&gt;
&lt;h2&gt;
  
  
  The Rebuild: Adding Micro-Interactions[Demo Code for understanding Purpose]
&lt;/h2&gt;

&lt;p&gt;I spent 6 hours today adding feedback for every state.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Button States
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleGenerate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Generate Sheet
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; 
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`
    transition-all duration-200
    hover:scale-[1.02] hover:shadow-lg
    active:scale-[0.98]
    disabled:opacity-50 disabled:cursor-not-allowed
    &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointer-events-none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
  `&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleGenerate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Spinner&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"animate-spin"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      Generating...
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Sparkles&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      Generate Sheet
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Impact:&lt;/p&gt;

&lt;p&gt;Hover: User knows it's clickable&lt;br&gt;
Loading: Shows something is happening&lt;br&gt;
Disabled: Can't double-click&lt;/p&gt;

&lt;p&gt;Providing the right UI makes or breaks the product. &lt;br&gt;
Building SpeakSheet in public.&lt;br&gt;
&lt;a href="https://x.com/NotVarunKV" rel="noopener noreferrer"&gt;@NotVarunKV&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Day 4 down. Many more to go.&lt;br&gt;
Questions? Drop them in the comments 👇&lt;/p&gt;

</description>
      <category>saas</category>
      <category>ai</category>
      <category>productivity</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Day 3: Fixed the Data Insertion Bug – SpeakSheet Now Generates Working Excel Files</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Fri, 14 Nov 2025 09:15:58 +0000</pubDate>
      <link>https://forem.com/not_varunkv/day-3-fixed-the-data-insertion-bug-speaksheet-now-generates-working-excel-files-1d9k</link>
      <guid>https://forem.com/not_varunkv/day-3-fixed-the-data-insertion-bug-speaksheet-now-generates-working-excel-files-1d9k</guid>
      <description>&lt;p&gt;TL;DR: The product now works. Excel files generate correctly with actual data.&lt;/p&gt;

&lt;p&gt;The bug: I was sending an object instead of an array.&lt;br&gt;
Time wasted: 2 hours.&lt;br&gt;
Lesson learned: Always log your data structures.&lt;/p&gt;

&lt;p&gt;Here's what happened.&lt;/p&gt;

&lt;p&gt;🐛 &lt;u&gt;The Problem&lt;/u&gt;&lt;br&gt;
Yesterday, SpeakSheet was generating Excel files... but they were completely empty.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3q2fm2fb7h3bg901jmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3q2fm2fb7h3bg901jmg.png" alt="The prompt the user provided and the excel file that is generated. This is done just by typing." width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The structure was there:&lt;/p&gt;

&lt;p&gt;✅ Columns&lt;br&gt;
✅ Headers&lt;br&gt;
✅ Formatting&lt;br&gt;
But no data was populating.&lt;/p&gt;

&lt;p&gt;🧠 &lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Data structure bugs are silent killers&lt;br&gt;
No error messages. No stack traces. Just... nothing works.&lt;br&gt;
These are the hardest bugs to debug because there's no obvious failure point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always log the EXACT data you're sending&lt;br&gt;
Don't just log console.log(data).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Small bugs block everything&lt;br&gt;
This one bug prevented:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing the full user flow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recording demo videos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Getting user feedback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Moving on to formulas/formatting&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fixing it unlocked everything.&lt;/p&gt;




&lt;p&gt;What's your most frustrating "it was just a data structure" bug?&lt;br&gt;
Drop a comment. I need to know I'm not alone in wasting 2 hours on square brackets 😅&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>nextjs</category>
      <category>saas</category>
      <category>ai</category>
    </item>
    <item>
      <title>Day 2: Building SpeakSheet – Hit My First Rate Limit and Learned Why JSON Schemas Are Evil</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Thu, 13 Nov 2025 19:34:47 +0000</pubDate>
      <link>https://forem.com/not_varunkv/day-2-building-speaksheet-hit-my-first-rate-limit-and-learned-why-json-schemas-are-evil-340p</link>
      <guid>https://forem.com/not_varunkv/day-2-building-speaksheet-hit-my-first-rate-limit-and-learned-why-json-schemas-are-evil-340p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Day 2&lt;/strong&gt; of building _SpeakSheet _in public – an AI tool that generates Excel spreadsheets from natural language prompts.&lt;/p&gt;

&lt;p&gt;Today was a mix of small UX wins and frustrating technical challenges. Here's what happened.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Shipped: One-Click File Uploads&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Problem:&lt;/u&gt;&lt;br&gt;
Users had to select a file AND click an "Upload" button before the backend would process it.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The Fix:&lt;/u&gt;&lt;br&gt;
Made the upload trigger automatically when a file is selected.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The Code (simplified):&lt;/u&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  const handleFileSelect = async (selectedFile) =&amp;gt; {
    if (!selectedFile) return;
    console.log
    if (validateFile(selectedFile)) {
      setFile(selectedFile);
      toast.success(`File "${selectedFile.name}" selected`);

      try {
        setUploading(true);

        // Use selectedFile directly, not file state
        const { publicUrl } = await uploadToSupabase(selectedFile, user.id);

        setFile(publicUrl);
        toast.success("File uploaded successfully!");
      } catch (error) {
        console.error("File upload failed:", error);
        toast.error("File upload failed");
      } finally {
        setUploading(false);
      }
    }
  };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;The Lesson:&lt;/u&gt;&lt;br&gt;
Sometimes the best features are the ones you remove. One less click = better UX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐛 Challenge #1: Supabase Storage Structure&lt;/strong&gt;&lt;br&gt;
&lt;u&gt;The Problem:&lt;/u&gt;&lt;br&gt;
Files weren't saving to Supabase storage, but no errors were showing in the console.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The Debug Process:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Checked file size limits ✅&lt;br&gt;
Checked bucket permissions ✅&lt;br&gt;
Checked network requests ✅&lt;br&gt;
Re-read the Supabase docs... and found it.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The Issue:&lt;/u&gt;&lt;br&gt;
A JSON schema error in my storage bucket configuration.&lt;br&gt;
Error because of this line &lt;code&gt;data[0].schema&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The fix:&lt;/u&gt;One missing pair of square brackets.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;The lesson:&lt;/u&gt; JSON schema errors fail silently and waste time. Always validate your config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge #2: Gemini API Rate Limits&lt;/strong&gt;&lt;br&gt;
Halfway through testing, I started getting this error:&lt;/p&gt;

&lt;p&gt;text&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: 429 - Resource has been exhausted (e.g. check quota).
First reaction: "Did I break something?"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;u&gt;Reality:&lt;/u&gt; I'm hitting rate limits already.&lt;/p&gt;

&lt;p&gt;&lt;u&gt;Quick fix:&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Swapped to a backup API key&lt;br&gt;
Kept building&lt;br&gt;
Long-term fix (needed):&lt;/p&gt;

&lt;p&gt;Add request queueing&lt;br&gt;
Show "High demand, please wait..." to users&lt;br&gt;
Consider upgrading API tier or adding fallback models&lt;br&gt;
The silver lining:&lt;br&gt;
Hitting rate limits this early means I'm actually stress-testing the product. Better to discover this now than during launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧠 What I Learned Today&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;UX wins come from removing steps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fewer clicks &amp;gt; fancy features&lt;br&gt;
Test with production-level load early&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hitting rate limits in dev &amp;gt; hitting them in production&lt;br&gt;
JSON schema errors are silent killers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Always validate your configs&lt;br&gt;
Rate limits are validation&lt;br&gt;
Means you're using the product enough to stress-test it&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>showdev</category>
      <category>devjournal</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Day 1: Building SpeakSheet – An AI-Powered Excel Generator (NextJS + Gemini + Python)</title>
      <dc:creator>Varun Krishnan</dc:creator>
      <pubDate>Wed, 12 Nov 2025 20:59:37 +0000</pubDate>
      <link>https://forem.com/not_varunkv/day-1-building-speaksheet-an-ai-powered-excel-generator-nextjs-gemini-python-ifh</link>
      <guid>https://forem.com/not_varunkv/day-1-building-speaksheet-an-ai-powered-excel-generator-nextjs-gemini-python-ifh</guid>
      <description>&lt;p&gt;I'm building my first SaaS product in public. It's called SpeakSheet, and it generates Excel spreadsheets from natural language prompts.&lt;/p&gt;

&lt;p&gt;This is my Day 1 devlog.&lt;/p&gt;

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

&lt;p&gt;🧠 &lt;u&gt;The Idea&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Most people create the same spreadsheets repeatedly. Budget trackers. Sales logs. Task lists. Client rosters.&lt;/p&gt;

&lt;p&gt;What if you could just describe what you need and get a fully structured .xlsx file in seconds?&lt;/p&gt;

&lt;p&gt;That's SpeakSheet.&lt;/p&gt;

&lt;p&gt;💡 &lt;u&gt;How It Works&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;User types a prompt (e.g., "Track gym members with name, age, membership type, join date")&lt;br&gt;
AI (Gemini Pro + LangChain) parses the prompt and extracts:&lt;br&gt;
Column names&lt;br&gt;
Data types&lt;br&gt;
Required vs optional fields&lt;br&gt;
Python backend generates the .xlsx file using openpyxl&lt;br&gt;
User downloads or receives it via email[Future Feature]&lt;/p&gt;

&lt;p&gt;✅ &lt;u&gt;What I Built Today&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Basic MVP UI (used ChatGPT to speed up scaffolding)&lt;br&gt;
Prompt → schema generation pipeline&lt;br&gt;
File upload feature&lt;br&gt;
Email/password authentication via Supabase&lt;/p&gt;

&lt;p&gt;💬 &lt;u&gt;Feedback Welcome&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;I'm building this 100% in public. Follow along:&lt;/p&gt;

&lt;p&gt;Twitter: [@NotVarunKV]&lt;br&gt;
Drop a comment if you have ideas, questions, or just want to roast my code 😅&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>saas</category>
      <category>discuss</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
