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.astroshipped 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/privacyto disclose what's actually loaded with a link to Vercel's analytics privacy policy. Followups outside the PR: deletePUBLIC_GA4_ID+PUBLIC_ADSENSE_PUB_IDfrom Vercel env (now dead config); optionally remove theG-JVG4TB1BR4property from analytics.google.com. - Tag cloud surfaced in main nav + reordered (PR #98) —
/tags/cloudexisted but was only reachable from a footer link on/tags/map. AddedTagsto main nav betweenMapandCuisine; active state lights up on any/tagsroute. 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 → Docsscreen (tina/AdminDocs.tsx) is the operator-facing handbook for IG sync, venue-tags scraping, pipeline status, and roadmap. Companion/admin → Cleanupscreen (tina/DataQuality.tsx) auto-detects two issue classes: 14 duplicate-venue groups (entries sharing a place_id) and ~170 suspect posts wheretitleandlocationfrontmatter 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 insrc/utils/data-quality.tswith 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/privacytwo weeks after merge, grep the served HTML forgoogletagmanager.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 witharia-current, cloud renders ≥5 chips (smoke for the venue-tags pipeline), and first chip's inline--remfont-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.pygained\bservice\b,\brepair\b,\bauto\bfilters plus\bpark(?!')\bso Park's BBQ stops being false-filtered (Python\btreats apostrophe as a word boundary). Lock-down testtest_venues_yaml_no_duplicate_keys.pyelevates silent PyYAML dupe-key tolerance to a pre-commit failure — js-yaml refused to load 33 silentcid: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 addedtodo #031capturing 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.pycalls Google'splaces:searchTextendpoint 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=NURL (the common case), the scraper navigates there, waits for Maps' delayed URL rewrite, extracts the FID hex from either the rewrittenlocation.hrefor the URL-encoded form embedded in sign-in continuation links, and writes the FID back tovenues.yaml. Each venue pays the resolution cost exactly once; future runs go direct via?ftid=<FID>. Full failure-mode write-up atdocs/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.pyfilters need tightening before further single-post sweeps. - 16 new unit tests —
scripts/venue-tags/test_lookup_place_ids_api.pylocks downextract_fid_hex,FID_HEX_RE,CID_RE, andwrite_yaml_field. Anchored to two real silent-fail modes lived through this session: the parser returningNoneon 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 withthirstypigwordmark + 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 tosrc/utils/regions.tswith 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 viathirsty-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 atscripts/build_favicon.py. (PR #75) - Local IG-watcher launchd agent —
scripts/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 existinginstagram-sync.ymlworkflow. Trims the manual export loop from "request → wait → download → manually create release → attach ZIP" to "request → wait → click email link → done." Companionweekly-monday-9amreminder 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.tsto match the new "Stories" / "Cities" / "Eat everything. Twice." / "Right off the stove" markers. Bonus discovery: a staleastro devserver on :4321 will leak dev-toolbar h1 elements into local Playwright runs viareuseExistingServer: !CI. Captured the whole pattern atdocs/solutions/test-failures/e2e-coupled-to-ui-text-after-rename.mdas 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
/privacypage added so the Meta App Settings could point atthirstypig.com/privacy. Note: the page's "no analytics" claims conflict with AdSense + GA4 + Vercel Analytics shipping inBaseLayout.astro; that drift is captured as a P1 todo (#004 intodos/) for triage. - 11 code review todos created — multi-agent
/ce:reviewsweep 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 intodos/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-coveron 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 → Testingshowing 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 + sharedpost_utils.pyhelpers. Meta-tests guard the intentional narrow-vs-broad dead-domain list distinction. - E2E batch for feature pages —
/hitlist,/search,/mapeach 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:
RelatedPostsskipped 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-commitrunsvalidate:hitlist + test:unit + test:pyin ~1.7 s before every commit. Plain shell hook, activated vianpm 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.ymlruns full E2E againstthirstypig.comdaily at 11:00 UTC. - Typecheck script —
npm run typecheck(plaintsc --noEmitwith 4 GB heap;astro checkOOMs on 2,120 post routes). Installed React + Google Maps types, fixed 2 pre-existing TS errors intina/*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.jsonnow 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; consolidatedDEAD_DOMAINSregistry intoscripts/post_utils.pywith two intentionally-scoped lists; replacedas anycasts inPostManager.tsxwith proper union types.
April 16–17 — Senior Front-End Audit (6 PRs)
- SVG logo — 25 KB JPG replaced with 650 B SVG (
currentColorfor theme adaptation). Every page loads lighter. - Image pipeline — New remark plugin transforms every markdown
![]()into<picture>with WebP source + explicit width/height (read viasharp) +loading="lazy".PostCardandBlogPosthero get the same treatment via a sharedgetImageInfo()utility. 7,503 previously-unused WebPs now actually serve. - Accessibility pass — Skip link,
aria-current="page"on active nav,aria-expanded/aria-pressedon toggles, dynamicaria-labelon the theme button,:focus-visibleoutline rule,@media (prefers-reduced-motion)override. - Contrast fixes — Light-mode
--color-stonedarkened from #78716C to #655F5B (4.3:1 → 5.6:1 on cream). Dark-mode--color-amberlightened 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.comCDN dependency); closed venues now render as dashed hollow rings so color-blind users can distinguish them; visible legend added. - Build cleanup — Latin-only
@fontsourceimports (33 woff2 + 33 woff files → 6 + 6, ~1.1 MB → 324 KB font bytes indist); print stylesheet that hides chrome, switches to system fonts, and reveals external link URLs. - Quick wins —
scroll-padding-top: 5remso anchors stop hiding under the sticky header;/posts-admin.jsondeindexed viarobots.txtDisallow +X-Robots-Tag: noindex; orphanedBaseHead.astrodeleted. - 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.