I Built a Christmas Gift Tracker in 2 Hours (My Kids Can't Know)

How I went from 'I need to track Christmas gifts' to a fully deployed, PIN-protected wishlist app before my morning coffee got cold.

dev astro claude parenting

It’s December 21st. I have four kids. I have no idea what I’ve already bought, what’s still in carts across 47 browser tabs, or which kid wanted the “cyber punk Sonic” (spoiler: that’s not a real thing).

So I did what any reasonable developer-dad does: I built a whole app instead of using a spreadsheet like a normal person.

The Problem

Every year it’s the same chaos:

  • Wishlists scattered across GoWish, texts, screenshots of TikToks
  • “Did we already get Elle the UGG slippers?”
  • “Wait, which kid wanted the iPad?” (Answer: all of them)
  • Budget? What budget?

I needed something that:

  1. Lives on my phone
  2. Is password protected (because my kids are nosy)
  3. Tracks spending per kid
  4. Lets me mark stuff as bought or “lol no”
  5. Works for Millie too

The Solution (2 Hours, Start to Finish)

I fired up Claude Code and described what I wanted. Here’s the actual conversation flow:

Me: “I want a XMAS section with PIN protection, wishlists for 4 kids, $500 budget per kid, track items with checkboxes…”

Claude: proceeds to build the entire thing

No joke. Two hours later I had:

  • A PIN-protected page (SHA-256 hashed, because I’m paranoid)
  • Four tabs for each kid with their wishlists
  • Budget tracking with a color-coded progress bar
  • Items sorted by store (critical for shopping runs)
  • “Got it” / “No way” buttons for each item
  • Product images and direct purchase links
  • A blurred preview behind the PIN modal (adds intrigue)

The Tech Stack

Nothing fancy:

  • Astro - because I already had a site
  • Tailwind - because CSS is a war crime
  • localStorage - state lives in the browser
  • Cloudflare Pages - free hosting, instant deploys

The whole thing is static HTML with client-side JavaScript. No database, no auth service, no $20/month subscription to some gift-tracking SaaS.

The Fun Parts

Scraping GoWish

Perry (14) uses GoWish for her wishlist. GoWish is a Next.js app that renders everything client-side, so I couldn’t just fetch the HTML. Instead, I had Claude write a Puppeteer script that intercepts the GraphQL responses:

page.on('response', async (response) => {
  if (response.url().includes('graphql')) {
    const json = await response.json();
    // Extract the goods
  }
});

Hacky? Yes. Works? Also yes.

The Image URL Nightmare

Here’s a fun one: I had Claude search for product images from various stores. The URLs it generated looked legit:

https://i5.walmartimages.com/seo/Some-Product_8e8c0f7e-7c9f-4e3e.jpg

Except… those were fabricated. The UUIDs were made up. Every Walmart and Target image was a 404.

The fix? Replace everything with Amazon CDN URLs. Say what you will about Bezos, but m.media-amazon.com URLs actually work.

Update: Plot twist - even the Amazon URLs were fabricated. The script was “finding” images but generating fake CDN IDs. The solution was a Puppeteer script that actually visits Amazon search results, extracts real product images, and validates they return 200 OK before using them. Node’s fetch() was lying about HEAD requests returning 200 when curl showed 404. Trust nothing.

PIN Protection Theater

The PIN is hardcoded. Is this secure? Against my 8-year-old? Absolutely. Against anyone with dev tools? Not even a little.

But here’s the thing - the data is in localStorage anyway. If someone’s inspecting my Christmas shopping in the browser console, I have bigger problems.

The real security is that my kids don’t know this page exists. And if they find it, the blurred preview behind the PIN modal shows them just enough to know they shouldn’t be there.

The Code

The whole gift item render function fits in ~50 lines:

function renderGiftItem(gift, kidIndex) {
  const priceFormatted = gift.price > 0 ? `$${gift.price.toFixed(0)}` : '';
  const imageHtml = gift.imageUrl
    ? `<img src="${gift.imageUrl}" class="w-10 h-10 object-cover" />`
    : `<div class="w-10 h-10 bg-neutral-700">🎁</div>`;

  return `
    <div class="gift-item flex items-center gap-2">
      ${imageHtml}
      <div class="flex-1">
        <div class="font-medium text-xs">${gift.name}</div>
        <span class="text-neutral-500">${gift.store}</span>
        <span class="font-bold">${priceFormatted}</span>
      </div>
      <button class="purchase-btn">✓</button>
      <button class="reject-btn">✗</button>
    </div>
  `;
}

State management is just localStorage with JSON:

const STORAGE_KEY = 'xmas2025_data';

function saveData(data) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}

function loadData() {
  const stored = localStorage.getItem(STORAGE_KEY);
  return stored ? JSON.parse(stored) : fetchFromJsonFiles();
}

That’s it. No Redux. No Zustand. No state management library with a logo and a Discord server.

Lessons Learned

  1. localStorage is fine - For personal tools, you don’t need a database. Your browser is the database.

  2. AI can scaffold fast - Claude built 90% of this. I directed, reviewed, and fixed edge cases. Total time: 2 hours including deployment.

  3. Amazon CDN is reliable - When you need product images that actually load, just use Amazon. Everything else is a gamble. But verify they actually work - don’t trust AI-generated URLs.

  4. Build validation into your workflow - We added npm run validate:images that checks every image URL before deployment. No more broken thumbnails in production.

  5. Your kids are smarter than you think - Hence the PIN. And not telling them about this blog post.

The Result

Four kids, four wishlists, one app. Budget tracking works. Millie can access it from her phone. Items persist across sessions. And next year? We’re combining households - eight kids, eight wishlists. This thing’s gonna get a workout.

Is it over-engineered for a problem a spreadsheet could solve? Absolutely.

Did I have fun building it? Also absolutely.

Will I use this next year? Probably. After I spend 3 hours in December 2025 trying to remember where I deployed it.


The XMAS tracker lives at a secret URL on this site. No, I’m not telling you the PIN. Nice try.


Merry Christmas 2024! Now if you’ll excuse me, I have 54 items across 4 wishlists to purchase before Sunday. The tracker says I’ve spent $0 so far. That’s about to change dramatically.