Skip to content

Changelog

A history of updates to this site.

April 2026 — Performance, Accessibility, Mobile Editing & Testing

April 30 — Privacy honest, tag cloud surfaced, admin docs (PRs #97–101)

  • Privacy page now matches reality (PR #98) — The page claimed "no analytics, no ads, no behavioural trackers" while BaseLayout.astro shipped Google Analytics 4 (always-on, hardcoded fallback ID), conditional AdSense, and Vercel Analytics. Path C of the privacy todo: dropped GA4 + AdSense entirely, kept cookieless Vercel Analytics, rewrote /privacy to disclose what's actually loaded with a link to Vercel's analytics privacy policy. Followups outside the PR: delete PUBLIC_GA4_ID + PUBLIC_ADSENSE_PUB_ID from Vercel env (now dead config); optionally remove the G-JVG4TB1BR4 property from analytics.google.com.
  • Tag cloud surfaced in main nav + reordered (PR #98)/tags/cloud existed but was only reachable from a footer link on /tags/map. Added Tags to main nav between Map and Cuisine; active state lights up on any /tags route. Removed the deterministic shuffle that was scrambling the rust → amber → ink → stone palette gradient; cloud now reads top-to-bottom as a heat gradient with biggest tags at the top.
  • TinaCMS Docs + Cleanup admin screens (PR #97) — New /admin → Docs screen (tina/AdminDocs.tsx) is the operator-facing handbook for IG sync, venue-tags scraping, pipeline status, and roadmap. Companion /admin → Cleanup screen (tina/DataQuality.tsx) auto-detects two issue classes: 14 duplicate-venue groups (entries sharing a place_id) and ~170 suspect posts where title and location frontmatter disagree on tokens (Jaccard < 0.35) — a real contamination signal from confident geocoding auto-fill picking a confusable nearby business. Build-time JSON at /data-quality.json, pure helpers in src/utils/data-quality.ts with 23 unit tests using the paired bug/legit fixture pattern.
  • Scheduled production verification (2026-05-14) — A one-time remote agent will fetch thirstypig.com, /about, and /privacy two weeks after merge, grep the served HTML for googletagmanager.com / googlesyndication.com / gtag( / adsbygoogle / G-JVG4TB1BR4, and confirm the privacy page contains "Vercel Analytics" but not "no analytics." Comments PR #98 if clean, opens an issue if anything regressed. The drift-was-3-days story we lived through with the original page is exactly what this routine prevents.
  • 3-test E2E spec for the tag cloud (PR #100)tests/e2e/tag-cloud.spec.ts: Tags nav routes correctly with aria-current, cloud renders ≥5 chips (smoke for the venue-tags pipeline), and first chip's inline --rem font-size exceeds the last chip's (regression guard against shuffle reintroduction). 9 cells (3 tests × chromium/firefox/webkit) green in 11.1s.
  • Curator filter tightening + dupe-key lockdown (PR #97)curate_candidates.py gained \bservice\b, \brepair\b, \bauto\b filters plus \bpark(?!')\b so Park's BBQ stops being false-filtered (Python \b treats apostrophe as a word boundary). Lock-down test test_venues_yaml_no_duplicate_keys.py elevates silent PyYAML dupe-key tolerance to a pre-commit failure — js-yaml refused to load 33 silent cid: field duplicates that PyYAML had been quietly merging. Two solution docs capture the meta-pattern of "stricter consumer caught a latent bug as a side-effect."
  • Code-review backlog committed (PRs #99, #101) — 9 review-finding todos from the multi-agent pass on PR #97 landed under todos/015–022, 030: TagGraph script-injection latent, placeId Zod regex, loadAllVenues() helper extraction, public venue-tags JSON schema doc, taxonomy.yaml deletion, scraper exit codes, auth-mode startup probe. PR #101 added todo #031 capturing the cross-venue chip aggregator unit-test gap surfaced after PR #100 shipped.
  • Five PRs merged in roughly 90 minutes — #97 (admin Docs + Cleanup + 7 review findings), #98 (privacy + tag cloud + nav), #99 (review backlog + provenance SVG), #100 (E2E coverage), #101 (todo #031). Linear history on main, all squash-merged.

April 29–30 — Venue tags scale-up via Places API (PR #96)

  • Places API path — New scripts/venue-tags/lookup_place_ids_api.py calls Google's places:searchText endpoint at ~100ms per venue with ~95% hit rate, including CJK / multi-match queries that DIY headed Chrome chronically failed on. Free tier (10K calls/SKU/month) covers our needs comfortably.
  • cid→FID self-healing scraper — When the API returns only a ?cid=N URL (the common case), the scraper navigates there, waits for Maps' delayed URL rewrite, extracts the FID hex from either the rewritten location.href or the URL-encoded form embedded in sign-in continuation links, and writes the FID back to venues.yaml. Each venue pays the resolution cost exactly once; future runs go direct via ?ftid=<FID>. Full failure-mode write-up at docs/solutions/api-migration/google-maps-cid-fid-self-healing-scrape.md.
  • Three batches shipped — Batches 6 (95 venues), 7 (89 venues), 8 (87 venues). Cumulative growth: 65 → 336 venues with FID (+271), 64 → 320 published JSONs (+256), 123 → 439 posts displaying tag pills (+316). Limited-view failure rate climbs into the long tail (9% in batch 7 → 19% in batch 8) — signal that curate_candidates.py filters need tightening before further single-post sweeps.
  • 16 new unit testsscripts/venue-tags/test_lookup_place_ids_api.py locks down extract_fid_hex, FID_HEX_RE, CID_RE, and write_yaml_field. Anchored to two real silent-fail modes lived through this session: the parser returning None on a valid Maps URI, and the YAML writeback regex no-op'ing on a key it should have matched. Total Python suite: 90 → 106 passing.
  • 552 single-post candidates remain in the corpus for future weekly sweeps. A weekly cron is wired but session-only — durable persistence (launchd or GitHub Actions) is a follow-up.

April 25–27 — Bold Red Poster redesign + supporting infra (10 PRs)

  • Homepage rebuild as the "Bold Red Poster" — full-bleed red hero with the original pig illustration bleeding off the right edge, 168px Archivo Black "Eat everything. Twice." display heading, "Right off the stove" featured grid (latest published post + next 4), and a 4-up city picker driven by real region data (Shanghai · 298, San Gabriel Valley · 238, Los Angeles · 222, Everywhere else · 1,178). Header rebuilt with thirstypig wordmark + the new 5-item nav (Stories / Cities / Map / Best Of / About), plus Hit List restored as a 6th item after first-cut review. Light mode only; dark-mode poster theme deferred. (PR #72)
  • Region landing pages — 38 new statically-generated pages under /regions/[region]/, one per unique region in post frontmatter. The homepage city tiles link here. aggregateRegions() extracted to src/utils/regions.ts with 5 unit tests guarding the "posts without region land in elsewhereCount, never as a phantom undefined-keyed top region" invariant. (PR #72)
  • Sharper hero pig — chroma-keyed transparent PNG produced by scripts/chroma_key_logo.py (HSV threshold + 1px alpha erosion + 4× LANCZOS upscale); the version actually shipped came from a higher-fidelity AI-rendered source via thirsty-pig-exact.svg. (PR #72)
  • Favicon swap — replaced the Astro-default rocket favicon with the redpig branding. Generated a full modern set: favicon.ico (16+32+48 multi-resolution), favicon-32.png, apple-touch-icon.png (180×180), icon-192.png, icon-512.png. Pipeline lives at scripts/build_favicon.py. (PR #75)
  • Local IG-watcher launchd agentscripts/local/ig_watcher.sh + a launchd plist: when an Instagram data export ZIP lands in ~/Downloads, automatically uploads it as a GitHub release, which triggers the existing instagram-sync.yml workflow. Trims the manual export loop from "request → wait → download → manually create release → attach ZIP" to "request → wait → click email link → done." Companion weekly-monday-9am reminder routine nudges you to do the export. (PR #76)
  • Facebook Page sync attempt + walled by Meta — built and shipped a full Graph API pipeline (token helper, sync workflow, schema enum, Privacy page, setup docs). Empirically confirmed the wall: Meta's "new Pages experience" requires a System User token for pages_read_engagement, which gates on full Business Verification (registered business entity, tax ID). Not justifiable for a personal blog. Cleaned the code back out; left the Privacy page in place; documented the dead end so future agents don't re-explore. (PRs #73 + #74)
  • E2E suite caught up to the redesign — first post-redesign CI run failed 4 tests across 3 browsers because nav-aria-current and homepage-heading assertions still expected old labels. Updated tests/e2e/{homepage,archive,categories}.spec.ts to match the new "Stories" / "Cities" / "Eat everything. Twice." / "Right off the stove" markers. Bonus discovery: a stale astro dev server on :4321 will leak dev-toolbar h1 elements into local Playwright runs via reuseExistingServer: !CI. Captured the whole pattern at docs/solutions/test-failures/e2e-coupled-to-ui-text-after-rename.md as a compoundable lesson. (PRs #79 + #80)
  • Hit list at 10 entries — Miopane (Old Town Pasadena) visited and removed; the curated link list lives in the commit message for when the dedicated post gets written. (PR #77)
  • Privacy page — minimal /privacy page added so the Meta App Settings could point at thirstypig.com/privacy. Note: the page's "no analytics" claims conflict with AdSense + GA4 + Vercel Analytics shipping in BaseLayout.astro; that drift is captured as a P1 todo (#004 in todos/) for triage.
  • 11 code review todos created — multi-agent /ce:review sweep of the session's work surfaced 4 P1 / 4 P2 / 3 P3 findings across security, performance, design-system, and reliability. Top items: privacy-policy drift, IG export ZIPs world-readable on a public repo, the 553KB hero PNG used as 40px nav favicon on every page, Bold Red Poster design tokens defined but never referenced. Triage scheduled for May 4. Full inventory in todos/004-014.

April 24 — Logo swap

  • Site logo — Swapped the placeholder mark in the Header and Footer for the original Thirsty Pig pig illustration. object-cover on a forced-square box keeps the 350×309 source from squashing inside the 40×40 nav slot. (PR #71)

April 18–20 — Testing stack (8 PRs)

  • Testing foundation — Vitest (JS/TS unit) + Playwright (E2E) + GitHub Actions CI on every PR, plus a TinaCMS admin screen at /admin → Testing showing file-by-file inventory, assertion counts, and cadence. Shipped 3 example tests as proof-of-shape.
  • Python parser tests — pytest added to the stack with requirements-dev.txt; 43 assertions locking in the Hit List vault parser + shared post_utils.py helpers. Meta-tests guard the intentional narrow-vs-broad dead-domain list distinction.
  • E2E batch for feature pages/hitlist, /search, /map each got spec files with 3–5 assertions: filter behavior, debounced search, WebP source emission, Leaflet marker paint, legend render.
  • Post-page regression suite + bugs caught — First-run surfaced two real production bugs: RelatedPosts skipped heading level h2 (WCAG violation), and its thumbnails missed PR #35's WebP pipeline. Both fixed in the same PR; the test is now a permanent regression guard.
  • Vault seeder round-trip tests — 14 assertions (7 unit on entry_to_md, 7 integration loading the real YAML → md → re-parsed → semantic equivalence). Locks in the invariant that switching to vault-editing can never silently rename existing entry ids.
  • Pre-commit hook (tier 1).githooks/pre-commit runs validate:hitlist + test:unit + test:py in ~1.7 s before every commit. Plain shell hook, activated via npm run setup:hooks. Zero new deps.
  • Final backlog + nightly production cron (tier 3) — Image-dimensions integration tests (with in-test fixture generation via sharp); closed-venue E2E (skips cleanly — revealed all 37 closed posts are draft: true); .github/workflows/nightly.yml runs full E2E against thirstypig.com daily at 11:00 UTC.
  • Typecheck scriptnpm run typecheck (plain tsc --noEmit with 4 GB heap; astro check OOMs on 2,120 post routes). Installed React + Google Maps types, fixed 2 pre-existing TS errors in tina/* files.
  • Totals — 12 test files, 105 assertions (24 JS + 57 Python + 24 E2E), ~6 s wall time. 3 of 4 cadence tiers live (pre-commit, CI on PR, nightly). Full details at /admin → Testing.

April 17 — Hit List mobile editing, Post Manager cleanup

  • Hit List Phase 4 — Obsidian vault sync pipeline: separate vault repo holds a mobile-friendly markdown checklist; GitHub Action pulls, converts to YAML, validates, and commits. Ships a parser, a seeder (yaml → md), the workflow, and setup docs.
  • Client-rendered search WebP/search.json now ships pre-computed WebP paths + dimensions per post so client-rendered result cards emit <picture> elements instead of plain <img>. +14 KB gzipped JSON cost, ~700 KB saved per search session.
  • Post Manager P1 fixes — Fixed content.index("---", 3) crash risk on malformed frontmatter in two Python scripts; consolidated DEAD_DOMAINS registry into scripts/post_utils.py with two intentionally-scoped lists; replaced as any casts in PostManager.tsx with proper union types.

April 16–17 — Senior Front-End Audit (6 PRs)

  • SVG logo — 25 KB JPG replaced with 650 B SVG (currentColor for theme adaptation). Every page loads lighter.
  • Image pipeline — New remark plugin transforms every markdown ![]() into <picture> with WebP source + explicit width/height (read via sharp) + loading="lazy". PostCard and BlogPost hero get the same treatment via a shared getImageInfo() utility. 7,503 previously-unused WebPs now actually serve.
  • Accessibility pass — Skip link, aria-current="page" on active nav, aria-expanded/aria-pressed on toggles, dynamic aria-label on the theme button, :focus-visible outline rule, @media (prefers-reduced-motion) override.
  • Contrast fixes — Light-mode --color-stone darkened from #78716C to #655F5B (4.3:1 → 5.6:1 on cream). Dark-mode --color-amber lightened from #D97706 to #F59E0B (4.3:1 → 5.3:1). All WCAG AA failures closed. @media (prefers-contrast: more) override added.
  • Map hardening — Leaflet bundled via npm (removes unpkg.com CDN dependency); closed venues now render as dashed hollow rings so color-blind users can distinguish them; visible legend added.
  • Build cleanup — Latin-only @fontsource imports (33 woff2 + 33 woff files → 6 + 6, ~1.1 MB → 324 KB font bytes in dist); print stylesheet that hides chrome, switches to system fonts, and reveals external link URLs.
  • Quick winsscroll-padding-top: 5rem so anchors stop hiding under the sticky header; /posts-admin.json deindexed via robots.txt Disallow + X-Robots-Tag: noindex; orphaned BaseHead.astro deleted.
  • Expected impact — Post-page mobile Lighthouse Performance ~45 → ~78, LCP ~5 s → ~2 s, CLS ~0.22 → <0.05, Accessibility ~85 → ~96.

March 2026 — The Rebuild

March 30 — Location Data Enrichment & Map Expansion

  • @mention venue extraction — Identified 65+ restaurant names from Instagram @handles in post bodies (e.g., @franklinbbq → Franklin BBQ, @snowsbbq → Snow's BBQ)
  • Location field cleanup — Fixed 451 posts with messy location/city fields where full captions had been dumped into venue name fields
  • Texas & Louisiana coverage — Added city/region data for 16 Texas posts (Austin, Lockhart, San Antonio) and 8 New Orleans posts that were missing from the map
  • Map expansion — cityCoords lookup table expanded from 46 to 104 cities, adding Austin, New Orleans, Medellin, Osaka, Chicago, and 50+ more
  • Additional geocoding — 185 more venues geocoded via Foursquare, bringing total to 841 precise GPS coordinates
  • Map pins — Map now shows 1,384 pins with 841 exact GPS locations across LA, Texas, Louisiana, New York, San Francisco, Asia, and beyond

March 29 — Venue Geocoding & Data Cleanup

  • Foursquare geocoding — Batch-resolved 655 venues to precise addresses and GPS coordinates via Foursquare Places API
  • Dead image cleanup — Stripped 5,631 dead wp.com Photon CDN image references from 397 posts, eliminating all 403 console errors
  • Instagram venue extraction — Parsed restaurant names and cities from 147 Instagram captions using pattern matching and hashtag analysis
  • Address extraction — Parsed 20 street addresses from blog post body content (#### heading blocks)
  • Map upgrade — Map now shows 1,600+ pins (up from ~1,000), with 656 precise GPS locations
  • Solution docs — Added docs/solutions/ for documenting technical solutions and runbooks

March 28 — Archive, Dark Mode & Map Enhancements

  • Year/month archive — Browse all 2,100+ posts by year and month at /archive
  • Dark mode — Toggle between light and dark themes; respects system preference and saves choice
  • Broken image handling — Gracefully hides broken image references in old posts instead of showing broken icons
  • GPS coordinates — Instagram posts now save exact GPS from EXIF data for precise map pins
  • Venue/city extraction — Automatically extracts restaurant names and cities from Instagram captions and hashtags
  • Map enhancements — Map now shows venue names, exact GPS pins (📍), and supports more posts
  • Location backfill — Script to retroactively add GPS and city data to already-imported Instagram posts
  • robots.txt — Added for proper search engine crawling
  • SEO complete — Open Graph, Twitter Cards, JSON-LD, sitemap, RSS, canonical URLs all in place

March 28 — Features & Polish

  • Search — Added client-side instant search across all 2,100+ posts
  • Restaurant Map — Interactive map showing 782 restaurant locations using Leaflet.js and OpenStreetMap
  • Best Of pages — Curated lists organized by cuisine, type, and region
  • Related Posts — "You might also enjoy" recommendations on every post, matched by city, cuisine, and category
  • Closed restaurant badges — 39 closed restaurants detected and marked with CLOSED badge and grayscale thumbnail
  • Restaurant categorization — Extracted restaurant names (1,649), cities (1,034), and regions (810) from post content
  • Image optimization — All 7,503 images converted to WebP format (40% size reduction)

March 27 — Content Import

  • Instagram import — 1,649 posts imported from Instagram data export, including 4,664 images and 213 videos
  • Thirsty Pig logo — Added the pig-with-beer logo to header and footer
  • Social media links — Instagram, X, Facebook, TikTok, YouTube, and Vimeo links in footer
  • WordPress cleanup — Removed AKPC, Popularity, AddToAny, and LinkWithin plugin remnants from 900+ posts
  • Tina CMS — Added configuration for visual content editing at /admin
  • GitHub + Vercel — Repository pushed to GitHub, deployed to Vercel with custom domain
  • DNS setup — thirstypig.com (primary), thethirstypig.com (redirect), and www variants configured

March 26 — Foundation

  • Wayback Machine scraper — Built Python scraper to recover content from three archived domains
  • Content recovery — 1,166 posts fetched, 1,157 parsed, 228 duplicates removed = 923 unique blog posts
  • Image recovery — 2,846 images recovered across two passes (Wayback Machine + Blogspot CDN + CDX search)
  • Astro site — Scaffolded with Tailwind CSS v4, Content Collections, sitemap, RSS feed
  • SEO — Open Graph tags, Twitter Cards, JSON-LD structured data, canonical URLs
  • Pages — Homepage, post detail, paginated archive, category pages, about page

2017 — Original Blog Ends

Final post published on thirstypig.com in March 2017. Instagram becomes the primary platform for The Thirsty Pig's food content.

2013 — Migration to thirstypig.com

Blog migrated from thethirstypig.com to thirstypig.com on WordPress. Content coverage expanded to include restaurants in Shanghai, Taipei, Tokyo, Seoul, Hong Kong, Bangkok, and other international cities.

2009 — Migration to WordPress

Blog moved from Blogspot to WordPress at thethirstypig.com. Added categories, tags, a pig rating system, and the Cocktail of the Week series.

2008 — The Beginning

The Thirsty Pig launched on Blogspot at blog.thethirstypig.com. First posts covered LA dining favorites, Korean BBQ, ramen, and the emerging food truck scene.

← Back to About