Platform Updates
Everything we ship, as we ship it. BGC is built in public by one person with a lot of caffeine and a small army of AI assistants.
July 2026
20 updatesYour location never leaves our servers
Working out which state you are browsing from used to involve handing your IP address to an outside company. That is gone. Location is now resolved by our own network edge before the request ever reaches us, so nothing about your visit gets shared with a third party.
Pages Load Straight to Content
Pages now render immediately instead of flashing a loading placeholder first. Most of the site loads faster than the placeholder animation could even finish one spin, so it was adding a flicker rather than covering a wait. Missing pages also now return a proper "not found" instead of quietly pretending they exist.
Range Weather Now Comes from the National Weather Service
Live conditions on range pages — temperature, wind, gusts and fire danger — now come straight from the National Weather Service instead of a third-party service. Same readings, official government source, and it keeps working when range pages get busy.
Sign-in surface tightened up
Retired a group of unused sign-in endpoints left behind by an older authentication system. Logging in with a password or with Google works exactly as it did before, with less machinery sitting behind it.
Faster Maps and Static Files
Map assets and other static files are now cached properly at the edge instead of being re-downloaded on every visit. Noticeable on map-heavy pages like range locations.
No more errors when traffic spikes
A flood of automated traffic could bog the site down badly enough to throw errors at real visitors. The bottleneck causing it has been removed, and pages that were being rebuilt from scratch on every single request are now served straight from cache.
Dead-store quarantine — honest ammo stock
Retailers whose product pages have gone dead (404 / hibernation) while their inventory feed still claims "in stock" are now auto-detected by a page-liveness probe and delisted from the market. "In stock" now means you can actually reach and buy it. Cuts phantom listings from ~13% to under 1%.
Honest ammo stock — verified before you see it
Every ammo listing is now checked for freshness. If a retailer hasn't confirmed a product in the last 7 days, it shows as 'Unverified' instead of a confident in-stock link — so the Best Price you click is real, not a phantom that sold out weeks ago.
Varied news hero images
Fixed news articles falling back to the same repeated photo — each article now gets a distinct, relevant hero. Cross-article image rotation added to the news image pipeline.
More diverse news, fewer repeats
The news pipeline now draws from 269 topic queries (up from ~100) across 14 categories, and dedupes on story identity — the case, statute, or the people and places named — so the same ruling can't get published three times under different headlines.
Retailer Shipping Costs
New shipping-policy scraper resolves real delivered shipping rates per retailer by requesting live quotes from each store's own cart API — the missing half of true cost-per-round. Loaded ammunition ships ORM-D ground with no hazmat fee; primers and powder carry one.
Phantom Stock Detection
New verifier checks whether the cheapest listings users actually see are really in stock, by asking each store about the specific product rather than trusting the bulk feed. Catches products deleted from a store that our catalog still advertises as available.
Rebuild Log Redesign
The Market rebuild log now reads as a timeline: each step shows its state as a node on a spine, with the step that is live right now marked and lit. Replaces the row of look-alike badges.
Sharper market search — comparison pages only
The market now points search engines at multi-retailer price-comparison pages and retires thin single-listing duplicates. Fewer, higher-quality pages means faster indexing and better search visibility for the products you're comparing.
Thumbs votes actually stick now
Root-caused why upvotes/downvotes never persisted: a database constraint rejected every vote the app sent. Fixed the constraint, added duplicate-vote protection, and votes now survive page reloads.
Comment on wall posts from public profiles
Wall posts on /@username profiles now carry full comment threads — the same threaded discussion system as the dashboard feed.
Attach photos to replies and comments everywhere
Every reply, comment, edit, and wall-post composer now has a photo attach button — dashboard feed, profile walls, detail pages, and Campfire. iPhone/Mac HEIC photos supported.
User profiles no longer 404 from feed links
Profiles with uppercase, dots, or underscores in the username (17% of members) were unreachable from feeds and comments. All profile links now resolve.
Message button always visible on profiles
The Message button on user profiles no longer hides when you are not mutual followers — it shows the requirement instead, and opens directly to your conversation once unlocked.
Business owners can moderate listing comments
Claimed business owners can now remove comments on their own listing, without waiting for a site moderator.
June 2026
8 updatesEvents show correct dates in their local time zone
Event listings now judge upcoming vs. past in each event's own time zone (resolved from its location), so a same-day event no longer briefly shows as 'past' in the evening. Applies across event pages, search, and listings nationwide.
Business corrections now apply on accept
Approving a submitted business correction used to create an empty duplicate listing and silently drop the submitted fields. Corrections now resolve the existing listing and update it in place — phone, website, email, address, and hours — and closure reports mark the listing closed.
Market product previews now rotate
Category previews on the market landing pages now rotate hourly through the top deals instead of showing the same products on every visit. Stays edge-cacheable, so SEO is unaffected.
AI vision-vetted article images
Hero images for news and reference articles now pass an AI vision check that rejects amateur, off-topic, watermarked, or low-quality photos. Re-imaged 200+ law, guide, and reference articles.
No more duplicate news articles
The news autopilot now runs one deterministic check before publishing, so the same story is never posted twice. Archived 6 existing duplicates.
26 new firearms news articles
Published a catch-up batch of current firearms and Second Amendment news spanning multiple states.
iPhone & Mac photo uploads (HEIC)
Profile and listing photo uploads now accept HEIC/HEIF images straight from an iPhone or Mac — they are auto-converted and optimized server-side. These uploads previously failed with a generic validation error.
Renamed & relocated listings now redirect instead of 404
Business and event pages whose URL changed — a renamed slug, a move to the correct state, or a merged duplicate — now 301-redirect to the current URL instead of returning a not-found page. Preserves inbound links and search ranking.
May 2026
11 updatesNational match events now filed under their correct state
Fixed ~840 Civilian Marksmanship Program national match events that were mis-assigned to Idaho — they now appear under the correct state events page with accurate map coordinates, pulled from the authoritative event source.
Sharper, Unique Article Images
News article images are now sourced by searching for the right shot (the actual firearm, person, or scene) across web image search and a curated photo library — every article gets a relevant, unique hero instead of a repeated or tangential stock image.
Per-State News in Navigation
State news (/{state}/news) is now reachable from the main and mobile navigation alongside directory, events, and ranges.
Author attribution on news articles
News articles now write the author_id (not just the display name) so they appear automatically on the author page. Backfilled 65 prior news pieces.
News Pipeline Rebuilt: Firearms-Only, National, Multi-State
The automated news pipeline now enforces a hard firearms-relevance gate so off-topic court coverage no longer slips in, spans every coverage pillar (legislation, hunting, competition, products, ranges, industry), and runs a per-state engine so coverage reflects the whole country.
Per-State News Pages
Each state now has its own news feed at /{state}/news, surfacing state-tagged firearms coverage alongside directory, ranges, and events.
Sharper Article Hero Images
Article images now resolve to the most specific relevant subject (the actual firearm, person, case, or place) instead of repeating a generic stock photo.
Self-Growing News Pipeline
The automated news pipeline now pulls from curated authority feeds (FPC, GOA, SCOTUSblog, state agencies) alongside a self-iterating discovery layer that grows its own queries each run. An AI editorial pass de-duplicates against already-published articles, ending recurring/repeat coverage.
Pipeline Credential Hardening
Data and content scripts now resolve database credentials from the environment instead of a stale hardcoded fallback, fixing silent auth failures across the automation suite.
Full-power Exchange listing editor
Sellers can now reorder photos by drag-and-drop, set any photo as the cover, add or remove photos, and edit the description, condition, and FFL transfer info inline — all in one editor. Edit is also available across active, pending, sold, and expired states.
Archived listings recovery
Archived listings now appear under a new Archived tab in the seller dashboard with a one-click Restore action. Accidental archives are no longer a one-way trip.
April 2026
98 updatesCanonical hours normalizer (no more whack-a-mole)
Audited every consumer of business.hours after three rounds of one-off shape fixes. The DB stores either legacy string-per-day, modern {open, close} object-per-day, JSON-encoded variants, or {general}/{text} placeholders — depending on data source. New src/lib/business-hours/normalize.ts is the single boundary that converts any input into a discriminated union ({kind: perDay, days} | {kind: general, text}). Wired through hero is-open badge, ticker bars, directory cards, hours sidebar card. The dashboard HoursEditor still reads/writes raw DB JSONB.
Detail pages — second hours-shape crash fixed
calculateIsOpen and the business-hours card both assumed string-per-day hours and crashed on the modern object-per-day shape ({open, close}). v2.119.5519 only patched the SEO schema parser. Now all three hours consumers (server hero status, ticker bar, hours card) handle both shapes.
Detail pages no longer crash on modern hours JSON
parseHoursForSchema in lib/seo/schema.ts assumed hours were stored as strings ("9am-5pm") but newer listings store {open, close} objects. The mismatch crashed every business detail page that had structured hours, with TypeError: b.toLowerCase is not a function. Now handles both shapes plus the closed:true marker.
Range satellite maps restored
Range card satellite maps were broken because the mbgl-renderer container had a stale Xvfb lock file from a prior crash. Cleared the lock and the renderer is back.
Forums back online
Campfire forums were stuck in installer mode after a Coolify redeploy wiped three of NodeBB's four volumes (config, build, uploads). The Postgres data volume survived, so all 115 users and threads are intact. Wrote a fresh config pointing at the existing database; build artifacts regenerated automatically.
Search no longer drops valid matches
Typesense was using its early-cutoff heuristic across multi-field queries, dropping legitimate name matches when description/city fields were sparse. Searching '2nd amendment firearm academy' (singular) failed to surface '2nd Amendment Firearms Academy LLC' even though the listing was indexed. Now uses exhaustive_search across business, event, location, and article collections, with query_by_weights pushing name/title above description fields.
Federation-aware search
Search now respects the state context of the page you are browsing. On /wyoming/* the header and hero search bars filter directory, events, and ranges to Wyoming; global pages (/, /handbook, /market) still search all states. Articles and market products remain global by design.
Classified messaging back online
User-to-user DMs (contact seller on classifieds, exchange messaging) were failing with 401s after a Coolify redeploy wiped the Matrix Synapse Postgres volume — every previously-issued access token was rejected by the new homeserver. Cleared the stale token rows so the auto-provision path takes over.
Self-healing Matrix tokens
Centralized comms account resolution into a single helper that validates stored Synapse tokens with /account/whoami before use and re-provisions on M_UNKNOWN_TOKEN. Future Synapse data loss won't silently break chat for hours — it heals on the next request.
Approved business submissions now visible immediately
Fixed a bug where community-submitted businesses approved through the moderation queue were stuck at status=unverified, making them invisible to search and the directory. Backfilled 9 affected listings approved between 2026-02 and 2026-04.
Fixed news article images
Re-sourced 14 bad news article images using explicit per-slug Wikipedia subjects, avoiding generic article leads (reciprocity tables, anti-gun PSAs, group portraits) that the brainstorm step had been picking.
Killed the auto-restart cycle (residual fetch leak via DB pool)
After the v2.118.5515 fix landed, the site was self-healing via auto-restart but still cycling every 2-6 hours (22 restarts in 48h). Root cause: 181K business + 84K event detail pages with revalidate=3600 ISR were still saturating the DB pool under crawler load; the timeouts then corrupted undici TransformStream state and leaked memory exactly like the fetch sites did. Same fix pattern as the depot pages — switched both to force-dynamic + Cloudflare edge Cache-Control (10min cache + 24h SWR). DB now only sees cold misses and revalidations. Auto-restart cycle should drop from every-few-hours to never (with daily 09:00 UTC restart still in place as belt-and-suspenders).
Fixed memory leak that caused 30-hour outage windows
Root-caused the second outage in two weeks: bare server-side fetch() calls were leaking undici TransformStream state per upstream failure, growing the Node process from 1.5GB to 9.6GB over 30 hours until the healthcheck timed out. Audited all 335 fetch sites (225 client/17 test/110 server). Built safeFetch wrapper (timeout + body.cancel on error — the leak fix). Swept all 65 server-side files to use it. Added auto-restart in the health monitor as a backstop (memory >4GB or unhealthy streak >=3) plus daily preventive restart at 09:00 UTC. Site is now self-healing in <5 min from any future leak.
Fixed site stability — depot ISR cache disk exhaustion
Root-caused and fixed the bug that was taking the marketplace down intermittently over the past ~3 weeks. Next.js default filesystem ISR was writing three files per unique product URL visited with no size cap — over 11M products, crawler traffic filled the container disk (peak 1.4M files / 136GB), timing out the healthcheck and returning 503s site-wide. Switched /market/depot, /market/builds, and /shooters per-slug pages to force-dynamic and moved caching to Cloudflare edge (bounded LRU). Added a static audit script (fails CI on any per-slug route with unbounded filesystem ISR) and a 5-minute health monitor cron that catches the pattern before it becomes an outage.
Crawl-budget hardening for search + filtered views
Noindex applied to paginated/filtered/searched directory and events pages; robots.txt now disallows free-text ?q= and ?search= URLs; tag links carry rel=nofollow to stop sculpting crawl into unbounded result space.
Related products: 2m 25s → 2.4ms
Product detail pages compute 'related products' by caliber/brand. Old query ran DISTINCT ON over market_products (10.7M rows) every page load — each call took 2+ minutes under load, stacked 50+ concurrent, pinned 10 CPU cores. Rewrote against mv_grouped_previews (2.7M rows, pre-aggregated). Query is now a UNION of two index top-K scans — idx_mgp_caliber_mincpr and idx_mgp_brand_mincpr — merged to top 8. Measured: 2.4ms, 27 buffer reads. ~60,000× faster.
getRelatedProducts cached
Product detail pages were stacking 50+ concurrent DISTINCT ON scans against market_products (10.7M rows), burning 10 CPU cores. Wrapped in unstable_cache keyed on (caliber, brand, category, limit) with 5-min TTL. excludeGroupHash filtered in memory post-fetch so the cache key stays bounded.
DB pool 15 → 50
Production drizzle pool was 15 connections. Crawler bursts exhausted it in milliseconds and every subsequent request timed out at connectionTimeoutMillis=10s, rendering site-wide "something went wrong". Postgres max_connections=100 so 50 leaves headroom for scripts + NodeBB + cron.
Stats bar restored via matview
Footer stats (businesses, events, ranges) were showing zeros because the connection pool was exhausted and 8 parallel COUNT queries all timed out. Rewrote getPlatformStats() to read a single mv_platform_stats row — one matview hit instead of eight COUNTs. Created the matview on prod (was only on dev).
Killed zone-wide Cloudflare purge
Matview refresh was calling purge_everything() on every run — nuking 55GB of edge cache per day. Removed the call; Next.js revalidate=300 ISR already handles freshness.
Trigram GIN indexes on listings
Manual migration adds pg_trgm GIN indexes on listings to speed up ILIKE %x% searches. Ships as CONCURRENTLY-safe standalone SQL.
Middleware 410 for crawler ?q= hits
Bot UAs hitting ?q= search URLs now return 410 Gone instead of running ILIKE scans. robots.txt already disallows these; 410 drains queued URLs faster than waiting for robots re-read. Real users unaffected.
Market crons: 6x/day → 1x/day
Shopify, WC, BigCommerce, matview refresh, and Typesense sync were firing every 4 hours, overlapping scrapers and hammering the DB. Collapsed to a single staggered window at 02:00-05:00 MTN (09:00-12:00 UTC).
Manufacturer connectivity probe
New scripts/discovery/probe_manufacturers.py — quick reachability check across 21 manufacturer catalogs before launching full discovery runs.
Spec backfill from title + handle + tags
New scripts/maintenance/extract_specs_from_title_handle.py — recovers ammo specs from existing product text without re-scraping raw sources.
Market results in search
Search page now renders market products (11M+) with dedicated card, filter pill, pagination. Previously dropped silently.
Market ammo data quality overhaul
Rewrote Shopify + WooCommerce normalizers to extract structured option metadata (Quantity, Caliber, Grain, Bullet Type, Casing, Barrel Length, Capacity, Magnification, Thread Pitch, etc.) and mine body_html descriptions that were previously dropped. Benchmarks on 32K Shopify variants: caliber detection 6.8% → 13.4% (2x), grain 0.8% → 3.5% (4.4x), bullet type 1.2% → 3.0% (2.5x). On 97K WooCommerce products: caliber 41.9% → 56.7%, round count 34.3% → 41.1%. 69-83% of rows now carry a product_specs JSONB payload (previously 0). CPR display hardened so undetected round counts render as em-dash instead of 0¢/round.
Market freshness dates actually fresh now
Root cause of the "as of April 2nd" bug: the scraper's delta check skipped writing last_seen_at when products had not changed. All three delta-skip paths (cadence backoff, atom-feed unchanged, ETag 304) now bump last_seen_at on every confirmation. Stamped all 10.7M existing rows with today's timestamp so the UI shows current dates immediately.
Search page redesign
Retokenized to BGC design system — rounded-xs cards, font-jetbrains stats, shadow-present/elevated, semantic colors. Replaced custom mesh gradient hero with brand-toned radial.
Server-side market pagination
Market search now paginates against Typesense directly, showing true total count instead of capped aggregator limit.
Smart matview refresh — skip if nothing changed
import_market.py --matviews-only now checks scrape_runs.products_updated since the last refresh. If zero scrapes wrote rows, skips the 8-matview rebuild entirely. Cuts ~1-2min of CPU+lock work per cycle when scrapers had nothing to do.
Smart Typesense sync — gates on matview freshness
sync_typesense.py for products collection now compares mv_market_stats.last_updated to system_state.last_typesense_sync_products. Skips if matview has not moved since the last successful sync. Eliminates 2.7M idle upserts per cycle when nothing changed.
Product classifier — firearms fixes
Added firearms safety net (complete-gun keyword + gauge/caliber → firearms) and fixed holsters regex matching "owb" inside "cowboy". Recovered 28,747 misclassified firearms.
4,607 new events imported
Range-calendar + gunshowtrader aggregator reruns after idle-in-transaction bug fix. Event total 107,501 → 112,108.
Market freshness dates honest again
refresh_direct.py now bumps last_seen_at alongside last_scraped_at, and backfilled 10.7M rows so /market no longer shows stale "as of April 2nd".
Comprehensive Mobile UX Overhaul
Audited and fixed 100+ mobile issues across ~50 components: responsive tables, 44px touch targets, iOS auto-zoom prevention, mobile TOC, sticky CTAs on product/business detail pages, crawlable pagination links, native share, filter drawer improvements, and more.
Google sign-in fix
Fixed Google OAuth login — Google recently changed their security headers (COOP: same-origin) which broke popup sign-in. Popup now closes properly and redirects to dashboard. Also fixed new user creation via Google.
Crawlable Pagination Links
Market category pagination now renders <a href> tags with rel=prev/next instead of onClick-only buttons — Googlebot can discover all paginated product pages.
Mobile Sticky CTA Bars
Product depot pages show a persistent Buy button with price, business detail pages show Call + Directions — both appear after scrolling past the hero, mobile-only.
Gzipped sitemaps fix 404
Static XML sitemaps were 404ing on prod because 386MB of uncompressed files were gitignored and never reached the Docker build. Switched to gzip compression (386MB→44MB), removed gitignore exclusion, and added proper Content-Type headers. 2.99M URLs across 71 sitemaps now deploy with the app.
Fix 404 status codes across all detail pages
Moved notFound() calls into generateMetadata() so they fire before Suspense streaming commits HTTP 200. Fixes 123K+ GSC "duplicate without canonical" errors caused by missing entities returning 200 with fallback canonicals instead of proper 404s. Affects market depot, directory, events, ranges, handbook, and author pages.
Google Search Console CLI
New CLI tool (scripts/core/gsc.py) for pulling search analytics data — top queries, top pages, daily trends, period comparison, winners/losers, sitemap status. OAuth2 user-credential auth with cached refresh token.
Social feed vote persistence fix
Thumbs up/down on wall posts now persists after page reload — feed API was missing auth cookie on fetch
SEO structured data recovery
Fixed JSON-LD structured data (LocalBusiness, Event, Article, BreadcrumbList, FAQ, HowTo schemas) being hidden from Googlebot by Suspense streaming boundaries. Moved all structured data to layout.tsx files that render outside the loading skeleton boundary. Also fixed root layout Organization/WebSite schemas from next/script to plain script tags.
Dropdown menu hover style fix
Menu items no longer flash solid slate-blue on hover — uses subtle muted background instead
Google OAuth login fix
Fixed Google OAuth login failing to set auth cookie — users completed Google auth but landed on Authentication Required page. Cookie is now set directly on the callback redirect response instead of relying on a fragile popup-to-postMessage chain.
TipTap editor focus border softened
Rich text editor no longer shows heavy gray border ring when focused — cleaner, quieter interaction
Theme labels corrected
Campfire and Night Ops theme names were swapped in the user menu — now match their actual appearance
Reaction button border removed
Emoji reaction trigger no longer has a prominent border at rest — matches sibling action buttons
5-tier ML classification pipeline
Product classifier now uses UPC lookback, fastText local ML (50K/sec), and Haiku API with signal cross-validation — rescued 292K products from other/unclassified.
PageSkeleton composition system
New skeleton primitive library replacing 47 hand-written loading files. Skeletons now share CSS layout classes with actual components — when layouts change, skeletons stay in sync.
Content Autopilot pipeline
Automated news discovery via SearXNG, AI editorial meeting (Haiku), article writing (Sonnet), Wikipedia image sourcing, and direct publishing. Self-evolving queries — each run suggests new search terms for tomorrow.
Owner address editing
Business owners can now update their address directly from the dashboard. Coordinates auto-geocode via Photon on save.
Unified platform CTA section
Consolidated transition, ecosystem cards, and bottom CTA into one cohesive block with ember glow bleed, pillar cards in 4-col layout, and full emotional arc
Procedural mountain OG images
Social share cards now feature seeded procedural mountain ridges with pillar-colored atmospheric glow behind the peaks.
Unified hero stat badges
Stats pulled out of hero subtitle text into consistent JetBrains Mono pill badges across all pillar pages. Events, Directory, Ranges, and Market heroes now show matching stat pills.
AI editorial meeting
Haiku reviews discovered stories, picks the best ones to write, kills duplicates of recent coverage, and suggests dynamic search queries for the next run. ~$0.01/run.
Market stats in homepage hero
Added product count to the sunrise hero stat grid — the market is the growth engine and now leads the stats bar
Smart article image resolver
Haiku brainstorms Wikipedia article titles for hero images, then a picker selects the best photo from each article's image gallery. No more capitol buildings or printing presses.
Section-specific OG images
Events, directory, handbook, ranges, exchange, and campfire pages now auto-select their own branded OG image for social sharing.
Platform stats materialized view
New mv_platform_stats matview replaces 7 live COUNT queries with ~1ms reads per state. All hero stats now served from a single pre-computed source.
Handbook terracotta hero identity
Added pillar-colored gradient tint to the Handbook hero carousel, giving it the same identity moment that Events, Directory, and Ranges have on arrival.
n8n Content Autopilot workflow
Scheduled 2x daily (7am + 5pm UTC) via n8n. SearXNG discovery in n8n, Claude writing via cron webhook. Fully automated content pipeline.
Sunrise hero redesign
Homepage hero rebuilt with procedural mountain silhouettes, stat bar, and section nav pills.
Design token harmonization
Unified card corner radius to rounded-xs across homepage, event cards, directory cards, and market listing cards. Fixed bottom-row hover shadow clipping in card grids.
DiscussionForumPosting schema
Campfire topics now emit structured data for Google rich results.
16 news articles published
First batch from Content Autopilot: Virginia AR ban, Kentucky concealed carry veto, Colorado 3D gun ban, SCOTUS suppressors, Hegseth base carry, and more across 8 states.
Primary Arms scraper
New NetSuite SuiteCommerce API scraper imported 19,630 products with full specs, UPC, and stock data.
Sitemap accessibility fix
Fixed middleware intercepting static XML/TXT files — sitemaps were returning 404 to search engines. Removed Cloudflare AI crawler block that was overriding our robots.txt.
E-E-A-T author profile improvements
Author pages now use ISR instead of force-dynamic. Article structured data consistently shows author job title. Bidirectional sameAs linking between articles and author profiles.
Skeleton drift detector
New Playwright test that compares loading skeleton bounding boxes against loaded page layout to catch structural divergence before deploy.
Public profiles at /@username
New public profile pages accessible without login at /@username. Shows profile hero, about section, brass rank & achievements, wall posts timeline. 38 components migrated to new URL scheme.
Homepage market product showcase
Replaced category image cards with live product browsing — firearms grid, ammunition table sorted by CPR, optics carousel, parts grid, and price drops. Same components as the /market landing page.
Homepage market hero with procedural mountains
New market-first hero section with MountainDivider, ember glow, animated stats, deal cards, category browser, ecosystem cards — all using design system Card/Badge/SlashLines components
National brand detail page enhancements
Added caliber distribution and product categories sidebar cards to national brand pages with new market queries
Homepage load time: 21s → 2.5s
Replaced expensive COUNT queries and GROUP BY aggregations with pre-computed materialized views (mv_market_stats, mv_market_category_stats, mv_homepage_deals)
Fixed cylinder loading animation getting stuck
Navigation progress cylinder now hides immediately on pathname change instead of waiting for opt-in content-ready signal. Added 5s safety timeout and tracked all timers to prevent orphaned timeouts.
Loading skeleton rewrites
Rewrote root, market, and campfire loading.tsx skeletons to match actual page layouts with proper section structure, grid columns, and sidebar placement
Typesense sync safety guards
Added require_prod_database() guard and file-lock competing-process prevention to sync_typesense.py. Prevents the exact failure mode that caused the 3-hour market outage on April 2.
Smarter product classification
Position-aware boundary scoring prevents complete firearms from being misclassified as parts/magazines when specs like barrel length or capacity appear in the title. Streaming classifier processes 2M+ products without memory spikes.
Handbook JSON parser overhaul
Rewrote article JSON parsing to handle Claude's unescaped content field (newlines, tabs, quotes). Structural boundary detection instead of regex.
Market data quality cleanup
Removed 850K+ junk products from non-firearms retailers (clothing stores, hardware stores, music shops, jewelry stores). Reclassified 100K+ products into correct categories. Rescued 71K firearms stuck in parts.
Zero-downtime Typesense sync
Full product reindex no longer takes search offline. New alias-based swap builds a fresh collection while the old one serves queries, then atomically switches.
Versioning system formalized
v2.FEATURES.FILES — features cumulative since v2.0, files is a lifetime odometer of total surface area touched. Never resets.
Product subcategories use retailer classification
Subcategory sections (Compound Bows, Riflescopes, etc.) now filter by product_type instead of title text search. Shows actual bows, not bow cases.
Honest deal badges
Great Deal badge now requires 2+ retailers carrying the product AND 20%+ savings vs group average. Was previously flagging 15% of firearms.
Smart market refresh pipeline
Shopify stores now use atom feed probes and ETag caching for delta detection — only fetching products that actually changed. Adaptive rate limiting prevents Shopify CDN blocks.
WooCommerce sitemap scraper
New WC scraper discovers product URLs via robots.txt and sitemaps, extracts prices from JSON-LD structured data with HTML fallback. Covers 1,558 stores vs 215 with the old Store API approach.
Priority-tiered product refresh
Refresh frequency now matched to product volatility: firearms and ammo checked every 4h, optics every 12h, gear daily, apparel every 3 days. Cuts unnecessary refresh volume while keeping high-demand products fresh.
Depot freshness indicators
Product detail pages now show "verified 4h ago" for recently checked products and "as of Mar 27" for older data, replacing the misleading "6d ago" timestamps.
Market category filters + sort + view persistence
Category pages now support spec-based faceted filtering via Typesense, custom price ranges, out-of-stock toggle, newest sort, and per-category view mode/sort preferences persisted in Zustand.
Brand normalizer expansion + title fallback extraction
348 new brand aliases added from unknown-brand audit. Import pipeline now extracts brand from product title when WooCommerce brand field is empty.
Social feed voting + reactions overhaul
Reviews now use real helpful_count and review_votes table instead of hardcoded zeros. Reactions expanded to all feed item types. Hot score uses logarithmic decay.
Market matview-only refresh mode
import_market.py --matviews-only flag for refreshing materialized views without running a full import cycle.
Fix market filters not applying — stale URL state bug
Filter clicks now accumulate correctly. Root cause: useSearchParams() stayed stale after replaceState URL updates, so each click overwrote the previous selection instead of adding to it.
Market filter API parallelized
Facets and products now fetched in parallel via Promise.all instead of sequentially. Single Typesense health check per request. ~40-50%% faster filter responses.
Typesense-powered market filters — 10x faster filter interactions
Filter API now uses a single Typesense multi_search call for products + facets (~30-80ms) instead of sequential PostgreSQL queries (~900ms+). Subcategory previews also batch through Typesense. PG fallback when Typesense is down.
Server-rendered category hero + optimistic filter UI
Category page hero (breadcrumbs, title, stats, image) now renders as instant server HTML — no JS needed. Filter sidebar checkmarks update optimistically before the API responds.
March 2026
178 updatesTypesense-powered faceted search for market
Amazon-style cross-filtered facets on all market category pages. 22 facetable spec fields (Type, Action, Caliber, Finish, Capacity, Barrel Length, Optics Ready, Frame, Sights, Threaded, Made In, and more). N+1 multi_search pattern — all facets in one HTTP call at ~400ms. PostgreSQL fallback for brand/caliber/price when Typesense is down.
Ducks Unlimited RSS feed integration
Official partnership with DU. 830 events synced from their RSS feed with images, venue data, geocoding, and auto-daily sync. Contact form routes to DU staff.
Direct-to-DB price refresh pipeline
Shopify + WooCommerce price/stock updates write directly to PostgreSQL — zero intermediate JSON files. Saves 60GB/month of disk waste. Both on 4-hour cron cycles.
Manufacturer spec enrichment
237K+ products enriched with manufacturer-grade specs from 29 PDF catalogs. Spec coverage: firearms 68%, ammo 33%, parts 58%, optics 43%.
Organizer contact privacy
Event organizer emails and phone numbers no longer displayed publicly. Contact goes through our form and routes server-side. Prevents scraping.
Static sitemap generation for 3.8M URLs
Decoupled sitemap generation from Next.js build. New Python script generates 90 pre-built XML sitemaps (3.85M URLs) in 25 seconds. Product groups from matview instead of scanning 11M individual listings. Daily cron regeneration at 3 AM MT.
Handbook Article Overhaul
541 articles audited. 4 articles fully regenerated via handbook pipeline (CZ 82, Walther P99, SP2022, First-Time Buyer Guide). 7 duplicate guides archived with 301 redirects. 30 smart brevity axioms stripped from guide articles. 474 word counts recomputed. 26 guides received curated tags. 9 meta descriptions generated.
Article Image Fixes
18 article images fixed: 3 critical replacements (Tikka favicon, Daniel Defense suspicious domain, PMC HTTP), 15 missing featured images filled from Wikipedia/Wikimedia Commons (10 guides, 5 state law capitol buildings).
Production deployment safety bible
Created PROD_DEPLOYMENT_BIBLE.md with safe patterns for matview refreshes (CONCURRENTLY), data syncs (swap-table), and schema changes. Added unique indexes on all 6 market matviews. Never TRUNCATE or lock-refresh on prod again.
11.7M products synced to production
Full market product catalog now live on prod — 11,713,542 products across 4,171 retailers with all 6 materialized views refreshed via CONCURRENTLY pattern.
Mobile Infobox Collapse
Handbook article infoboxes now collapse on mobile devices. Firearms specs, legal details, and other infobox types show as a expandable toggle instead of rendering 800+ pixels of data above the article content.
Typesense health check caching
Cached Typesense health check for 30 seconds to prevent 5-second timeout penalty on every search request when Typesense is unavailable. Reduced connection timeout from 5s to 2s.
Salomon Firearms: Products → Events
Recategorized Salomon Firearms Training (Swansea, MA) from market retailer to training provider. Removed 367 misclassified market products. Ingested 27 future training courses as scheduled events from their WooCommerce catalog.
Market Filter Sidebar
Amazon-style filter sidebar for all market category pages with sticky positioning and mobile drawer.
Static Map API Rewrite
Self-hosted static map generation via mbgl-renderer replacing Mapbox Static Images API.
Map Tools: Measurement, Weather, Waypoints
Distance measurement (click-to-click polyline with drag), area measurement (polygon with acreage), point weather via Open-Meteo (temp, wind, humidity, pressure), user waypoints with account persistence, historic wildfire perimeters from NIFC.
Map: Unified Control Panel
Single control panel replaces 3 old components. Style switcher with icons, layer chips, tool buttons, map key with elevation gradient, attribution. Responsive bottom sheet on mobile. Gear FAB bottom-right.
Map: Diamond Bracket Pins
RTS-style diamond bracket markers replace plain circles. Canvas-generated at runtime, section-colored per variant (directory/events/ranges/market).
Map: Public Lands Overlay
BLM, USFS, NPS, BIA, DOD boundaries as dashed outlines with agency labels. No color fill — elevation tint shows through. Toggleable via control panel.
Map: Fullscreen Portal Fix
All map overlays render via React portal to document.body, escaping sticky navbar stacking context. z-index 9999 now works correctly.
Map: Sidebar Expand-to-Fullscreen
Sidebar maps get a small expand button. Click to go fullscreen with full controls. No controls visible in sidebar view.
BGN: 100%% Entity Coverage
Backfilled BGNs for 211 businesses and 3 articles. All entity types now at 100%% coverage: 181K businesses, 97K events, 526 articles, 36 orgs, 1 exchange listing.
Patterson cross-blend hypsometric tinting
Desaturated elevation ramp to value-only undertone with landcover hue tints overlaid — forest reads green at any elevation, scrub reads tan, farmland reads gold. Three-layer compositing: elevation=height, landcover=type, hillshade=shape.
Self-hosted Mapterhorn DEM Tiles
Swapped AWS terrain tiles (256px PNG) for self-hosted Mapterhorn DEM (512px WebP). 4x pixel density, Copernicus GLO-30 + USGS 3DEP source data. Zero external dependency — all map tiles now served from our infrastructure.
Map Tile Coverage: Alaska, Hawaii, Puerto Rico, USVI
Expanded PMTiles coverage from CONUS-only to all US territories. Alaska, Hawaii, Puerto Rico, and US Virgin Islands now have full vector tile data.
3D building depth and figure-ground
Added directional warm-amber light for fill-extrusion shadow, height-based color variation on buildings, 2D footprint outlines at z12-14, and residential/commercial landuse masking to neutralize elevation tint in cities.
Map tile sharpness overhaul
Retina pixel ratio, antialiased WebGL canvas, doubled tile cache, cancelled stale tile requests during zoom, instant tile swaps. Near and mid-ground stays crisp at 45° pitch.
Server: 400GB Disk Reclaimed
Deduplicated 15,637 raw scrape files (320GB), offloaded 68GB migration archives to S3, deleted old project copies. Disk usage dropped from 96% to 56%.
Cleaner road and boundary rendering
Removed road casing layers (dark border + white center AA artifacts) and county boundary clutter. Highways keep casings for distance readability, state borders stay.
Water depth rendering
Zoom-based water color shift (deep teal at regional → lighter at street level) plus shoreline edge strokes on all water polygons.
Market: Price Comparison Engine
All product sections now show grouped comparison cards linking to depot price comparison pages. Hero cards show real cheapest deals from getCategoryBestDeals query. Every product card leads to multi-retailer comparison, not individual retailer links.
BGC Entity Registry (BGN) — Canonical Identifier System
Every entity on the platform now has a permanent, human-readable Boise Gun Number (BGN). 10-character identifiers (like Amazon ASINs) for all 6.7M products, 181K businesses, 97K events, and 523 articles. Includes resolution API at /api/bgn/{bgn} with merge chain support.
Market: Price History & Daily Snapshots
Daily price snapshot cron captures pricing across 900K+ product groups. Price history charts and sparklines now have data. Non-ammo categories get price context badges (Great/Good/Avg) via category median matview.
E-Commerce Discovery Pipeline — 10,761 Stores Detected
Built a reusable platform detection utility that probed 36K+ business websites. Identified 10,761 stores with confirmed e-commerce platforms: 5,159 WooCommerce, 1,018 BigCommerce, 757 Shopify, 143 AmmoReady, and more. New scrapers built for BigCommerce and AmmoReady shared catalogs.
Marketplace Expansion — 7.5M Products, 1,650+ Retailers
Scraped 1,100+ new WooCommerce and Shopify retailers, adding millions of products to the marketplace. Price comparison now covers ammunition, firearms, optics, parts, magazines, reloading supplies, holsters, archery, airguns, and more across 1,650+ retailers.
Map System: Hypsometric Elevation Tinting
Real elevation-based color ramp using MapLibre color-relief layer type. Teal sea level through emerald lowlands, golden valleys, amber foothills, terracotta mountains, slate alpine peaks. Visible across entire USA.
Map System: 3D Buildings
Buildings render as 3D extruded blocks at zoom 14+ using fill-extrusion layer. Height from OSM data where available, 6m default. Fades in gradually for smooth transition.
Dashboard Feed — Rich Content Rendering Fixed
Wall posts, check-ins, photo uploads, and reviews now render formatted text correctly instead of showing raw HTML tags. Likes and reactions now persist across page refreshes. Image replies added to comments.
WooCommerce Refresh Increased to Every 4 Hours
WooCommerce price refresh frequency doubled from twice daily to every 4 hours with 30 parallel workers. Each WC store is an independent server with no shared rate limit, so higher concurrency is safe.
Map System: Space Grotesk Font
All map labels switched from Noto Sans to Space Grotesk. Generated PBF glyph files for Regular/Medium/Bold and uploaded to self-hosted font server.
Map System: 15 Land Type Layers
Expanded from 3 to 15 distinct land type fills — forest, scrub (tan for sagebrush), grassland, farmland, barren, glacier, urban, parks, sand, military, cemetery, golf. Idaho landscape now shows realistic desert/scrub south of Boise.
Map Widget Gallery
New /tmp-map page with layer debugger — toggle every map layer independently, sliders for terrain exaggeration and pitch, all map component variants displayed.
Data: 1.28M Corrupt Hash Collisions Fixed
Nulled product_group_hash on mega-collision groups (>500 products per hash). Rebuilt mv_grouped_previews matview. Added avg_price column computed from real AVG, not midpoint.
Submit "+" button added to navigation
Persistent rusty-orange + button in both desktop and mobile nav for quick access to content submission. Previously the only link to /submit was buried in the footer.
Universal Command Bar
New unified command bar system across all pillar pages — Market, Events, Directory, Ranges, Handbook. Sticky filter pills, search, sort, view modes, subcategory ledges, and accent glow lines per pillar.
Handbook Section Redesign
Guides, laws, news, reviews, scores, and wiki pages now use content-river components with featured carousels, topic pills, and data-driven layouts instead of pass-through tab wrappers.
GlitchTip error sweep — 14 code fixes, noise filters
Fixed null crashes in directory cards, news views, compete page, wiki infobox, and market categories. Added comprehensive Sentry filters for WebGL, ChunkLoad, hydration, and browser noise. Resolved all 200+ GlitchTip issues.
Retailer submission page for price comparison
New /submit/retailer page for store owners to get their products listed in the price comparison engine. Collects store details, e-commerce platform, product count, and contact info. Free listing — no API integration cost.
Activity-Based Range Filters
Ranges page now filters by Trap & Skeet, Pistol, Rifle, Archery, and Indoor based on actual facility data — not just location type.
Market Landing Page Redesign
Complete market hub redesign: 12-column bento category grid with hero/mid/compact cards, Wikimedia images, accent gradient overlays, exchange CTA dark breaker, ammo table/card toggle, value banner.
Exchange → Market rename — full codebase consistency
Renamed all Exchange references to Market across 106 files: types, queries, API routes (/api/exchange → /api/market-listings), dashboard, legal, components, and email templates. DB table names preserved.
Submit hub redesigned with B2B sidebar
Submit page now has two-lane layout: community submissions (events, reviews, articles) on the left, business tools (List Your Store, Claim Listing, Advertise) in a sticky sidebar on the right with live platform stats from the database.
Stockpile → Market rename + dead code cleanup
Renamed StockpileStats/StockpileCaliberOption interfaces and functions to Market equivalents. Removed unused getActiveExchangeDeals and image search service dead code.
Comment Image Uploads
Users can now attach images directly in comments. Images are uploaded, sanitized, and rendered inline.
Detail Command Bars
Sticky contextual info bars on all detail pages (events, businesses, ranges, products) showing key metadata — date, venue, rating, phone, price, caliber — with back navigation to parent pillar.
Market Mega Menu Expansion
3-column mega menu for Market with all 12 product categories, C2C exchange, retailer tools, and live stats strip (6.5M products, 1,261 retailers).
Dynamic state counts + hydration fixes
Replaced hardcoded 51 state count with FEDERATION_STATES.length in 3 components. Added suppressHydrationWarning + timeZone UTC to date rendering in 4 client components.
Mega menu updated with retailer links and fixed stale text
Added "List Your Store" to Market and Directory mega menus. Fixed "200+ retailers" to use generic language (actual count served from DB on market pages). Fixed Claim Listing href inconsistency.
Product classifier rescue layer — 6.5M products reclassified
Added rescue signal system that overrides incorrect Tier 1/Tier 2 classifications. Training sessions, airsoft products, compensators, slides, cleaning kits, and mounts now correctly categorized instead of defaulting to firearms. Parallel 4-worker classifier completes in 17 minutes.
Homepage Market Integration
Market stats on homepage hero (Products count), Compare Prices nav pill, and market data in /api/stats endpoint.
Market Filter Sidebar
Unified ammo filter sidebar with search include/exclude, sort pills, caliber/brand dropdowns, price checkboxes — all URL-param driven for server-side filtering.
Market Performance: 500ms Hub Page
Created 5 PostgreSQL materialized views replacing live queries against 6.5M products. Hub page went from 30s+ timeout to 500ms. Category pages from 5-7s to 0.7-1.5s.
Manufacturer Product Catalog System
Built canonical product database scraped from 35 manufacturer websites and PDF catalogs. 6,020 products with 89% structured spec coverage. 19 PDF catalogs downloaded (292MB), Claude Haiku batch extraction, and retailer-to-manufacturer linking across 6.5M listings.
Fixed map pin not showing on business detail pages
Business hero card map was missing the MapLibre CSS import, causing the location pin to be invisible despite being in the DOM.
Fixed map labels rendering above cluster pins
Cluster circles and individual pins were being inserted below map label layers, making them hidden under city names and road labels. Now render on top.
Price Comparison Trust Markers
Comparison table now shows retailer names with favicons, data freshness timestamps, round counts, and value badges. Single-retailer products shown with muted styling.
Remove Auto-Sponsored Listings
Removed fake sponsored badge system that flagged unclaimed enriched listings as sponsored. Real sponsor infrastructure (is_sponsored, tiers) preserved for future paying customers.
Re-enriched 1,791 shooting range descriptions from verified source data
Replaced hallucinated AI descriptions on WhereToShoot ranges with accurate information from the NSSF structured API data — correct public/private status, actual shooting disciplines, and real amenities.
Price History Charts
Built price history tracking with daily snapshots (937K groups captured). SVG sparkline and full chart components for product detail pages. Cron endpoint for daily price captures.
Marketplace Renamed to Market
Consolidated marketplace → market naming across all routes, queries, types, and components. Cleaned up dead code and duplicate files.
Cleaned up 17,320 stale events and fixed 13,168 event state assignments
Archived past events stuck as scheduled, reverse-geocoded low-confidence events via Photon to correct state assignments, and removed 39 news articles that were incorrectly imported as events.
Product Quality Scoring Pipeline
Added quality_flags and price_trustworthy columns. Pellet/airgun override prevents .177 caliber products from appearing in ammunition results. Suspect CPR and price outliers flagged.
Fixed Stale Product Timestamps
Import pipeline was using hardcoded March 17 date for all products. Now uses current datetime. Next scraper run will show fresh timestamps.
Consolidated event categories from 41 to 6
Merged fragmented event categories into clean buckets (competition, show, training, community, conservation) while preserving granular event_type for sub-filtering.
Added 150 websites to shooting ranges via Serper discovery
Ran website discovery pipeline on 1,937 ranges without websites. Found 58 via fuzzy matching and 92 via manual review of search results.
Business verification form now works
The "Suggest an edit" link on directory listings was submitting to a missing API endpoint. Created /api/submissions/verification — corrections now save to the moderation queue and email admins.
Corrected 7,346 business state assignments via geocoding
Reverse and forward geocoded 14,088 low-confidence businesses via local Photon. Over half were in the wrong state — now showing in correct state directory pages. Added coordinates to 14,028 businesses that had none.
Hero descriptions no longer show raw markdown headings
Business, location, and national brand detail heroes were rendering full formatted markdown (## headings, bullets, tables) instead of a clean text excerpt. Now shows a plain-text 300-char summary with a see-more link.
Classified 2,186 uncategorized events by title
Regex-based classification assigned category and event_type to all remaining null events. Zero uncategorized events remain.
API anti-scrape hardening
Directory and events nearby endpoints capped at 100-mile radius and 50 results. Map-markers and nearby routes require same-site Referer. Data endpoints rate-limited to 20 req/min. x-powered-by header stripped.
Unlisted 4,437 non-firearms businesses from directory
Removed meat processors, churches, law firms, TV stations, and other serper discovery junk that was incorrectly active. Preserved legitimate ranges and industry exhibitors that were misclassified.
CWV Performance Refactor
Server-rendered business detail hero with client islands. Eliminated layout shift and reduced TTI on directory detail pages.
Multi-category marketplace engine
Marketplace expanded from ammo-only to full product comparison across firearms, optics, parts, magazines, holsters, reloading, and NFA. Category-specific spec schemas, product detail pages with FFL context, and restructured hub page.
Dynamic OG Image System
Built branded social sharing images using Next.js ImageResponse with self-hosted brand fonts. Pillar-colored OG images for events, directory, handbook, ranges, exchange, and campfire. Dynamic /api/og route generates unique images per entity.
Stockpile product specs system
PCPartPicker-style structured specifications (JSONB) for every product category — ammunition ballistics, firearm dimensions, optic specs, part compatibility. Extracted from retailer pages and displayed on product detail pages.
SEO Metadata Hardening
Fixed 17+ critical SEO issues: noindexed auth/submit pages, added OG+Twitter cards to 10K+ campfire topics, fixed broken homepage canonical URLs, capped meta descriptions at 160 chars, standardized Twitter card fields, fixed event venue schema fallback.
Sitemap expansion — products, brands, orgs
Sitemap now includes stockpile products, national brand pages, organization pages, and marketplace category pages. Segmented pagination for 330k+ product URLs.
Generic product scraper + WooCommerce Round 2
New generic_product_scraper.py for non-Shopify/WC platforms. 600+ WooCommerce retailers added. Sitemap-based product discovery pipeline.
Handbook JSON parser hardened
Reordered JSON recovery stages — control character fixing now runs before brace matching, fixing 15-20% of truncated article parse failures.
Schema.org moved to page components
JSON-LD for national brands and organizations moved from metadata.other to proper Script tags in page components, fixing Google structured data validation.
Build optimization overhaul
Researched and locked down the production build configuration after 5 failed deployments. Restored working webpack base (build worker + memory optimizations + filesystem cache + splitChunks), added static generation limits (cpus: 2, workerThreads: false), explicit source map disabling, and Sentry deprecation fix. Build context slashed from 1.33GB to ~400MB via .dockerignore overhaul.
Event submission state fix
Fixed a bug where all user-submitted events defaulted to Idaho regardless of actual location. Event form now includes state (required dropdown) and ZIP code fields. Corrected 6 misrouted events in production (AZ, TX, DE, AL, MS).
PostGIS Spatial Indexing
Added geography columns and GIST spatial indexes to listings, events, and shooting_locations tables. Proximity queries now run in ~7ms across 128K+ businesses. Auto-populate triggers keep geog column in sync.
Shopify scraper reliability overhaul
Fixed 68% failure rate in product scraper — root cause was Shopify CDN per-IP rate limiting triggered by concurrent requests. Switched to sequential processing with 0.5s pacing. Now 75/76 retailers succeed (99%), products scraped jumped from 21K to 133K per run. Fixed manifest cross-contamination bug in --retry mode.
Product catalog doubled to 338K
Successful scraper fix recovered 50+ previously failing retailers, bringing total indexed products from 168K to 338K across 75 Shopify stores.
Public Land Boundary Overlay
BLM, USFS, NPS, BIA, and DOD public land boundaries for 9 western states (ID, MT, UT, NV, AZ, WY, CO, OR, NM). Self-hosted as PMTiles, toggleable on all map views. Color-coded by agency.
Multi-Stop Trip Planner
Directions modal now supports adding waypoints between origin and destination. Add stops by searching or clicking nearby businesses along the route. Valhalla handles multi-leg routing with per-stop distance/time breakdown. Exports to Google Maps with waypoints.
Near Me Geolocation
Use My Location button on directory, events, and ranges sidebars. Browser geolocation with reverse-geocode display. Events page now has full distance filtering parity with directory and ranges. Auto-sorts by distance when location is shared.
PostGIS Proximity API
New API endpoints /api/directory/nearby and /api/events/nearby return results sorted by real distance using PostGIS ST_DWithin. Upgraded /api/routing/nearby from bounding box to spatial index.
Map Vector Skins
Restored 5 branded map skins (Dawn, Day, Noon, Evening, Night) that were hidden behind a satellite redirect. Fixed labels (lang parameter was missing), added hillshade with multidirectional rendering, corrected sprite theme switching, enhanced state boundary visibility.
Terrain Topo Style
New Terrain map style with hypsometric tinting (color-relief elevation ramp), per-theme sun angles for hillshade, contour lines with elevation labels, and 3D terrain exaggeration. Uses MapLibre v5.20 color-relief layer.
Highway Shield Renderer
MUTCD-accurate highway shields (Interstate blue/red, US Route black/white) via @americana/maplibre-shield-generator. Renders dynamically at runtime from ShieldJSON definitions.
Map UX Improvements
Imperial scale bar on all maps, cooperative gestures to prevent accidental zoom, hover highlighting on map pins with GPU-accelerated feature state.
Map Dead Code Cleanup
Removed 3,673 lines of dead map code — 9 orphaned hooks, 5 unused components, 3 npm packages. Map system reduced from 12 hooks to 6, all rendering in production.
Marketplace product taxonomy — 160K products in 13 categories
The Marketplace now shows all 160K products organized into 13 categories: Ammunition, Firearms, Optics, Parts, Magazines, Holsters, Reloading, Cleaning, Gear, Knives, Airguns, Archery, and NFA/Suppressors. Each category has a clickable card with product count and a preview row with View All links.
Reloading Data Scrapers
11 automated scrapers collecting reloading component reference data — 895 powders with burn rates, 220 SAAMI cartridge specs, 106 bullets with BCs from 7 manufacturers, 39 primers, and Hodgdon RLDC load data integration. Foundation for The Bench reloading calculator.
Exchange renamed to Marketplace
The Exchange is now The Marketplace — reflecting the expansion from C2C classifieds to a full product aggregator with 200K+ products from 87+ retailers. All old /exchange URLs permanently redirect to /marketplace.
Real ammo price aggregator — 6,429 products from 26 retailers
The Exchange ammo section now shows real product data from 26 online ammo retailers. Every listing links to an actual product page with real prices. Sortable table view with inline caliber and brand filters, plus card view with DirectoryCard design language.
Ammo import pipeline (Holy Script #18)
New import_stockpile.py parses raw Shopify product JSON from 585 retailer files, detects caliber/grain/bullet type/casing from titles, and upserts into stockpile_products table with ON CONFLICT dedup.
Ammo aggregator expanded to 17,500+ products from 53 retailers
Parser now handles both Shopify and WooCommerce product formats. Title-based ammo detection catches retailers with empty product_type fields. Up from 6,429 products / 26 retailers.
Category-specific marketplace layouts
Each product category now has its own visual treatment: Ammunition shows a hero deal card with CPR ranking, Firearms uses a staggered featured grid, Optics has horizontal scroll with price comparison, Parts uses a dense 4-column grid, and Reloading includes a cross-sell for The Bench calculator. All with real product data from 87 retailers.
Marketplace product cards — Hero, Grid, and Row variants
Three card variants for the product aggregator: Hero cards with white image zones and full metadata for featured products, Grid cards as the 4-across workhorse with image fallbacks and type badges, and compact Row cards for ranked lists. Every card shows retailer name, stock status, keyword-detected type badges (firearm type, optic type, platform compatibility), hover lift animations, and click tracking.
Product grouping + comparison table
Same product from multiple retailers collapses into one row showing price range, retailer count, and savings spread. Price context badges (Great Deal / Good Price / Average / Above Avg) computed from per-caliber median CPR. Click any row to see all prices side by side on the Depot comparison page.
Category deep-dive pages
/marketplace/ammunition (and all 13 categories) now has a full-page comparison experience with caliber filter chips, search, sort by CPR/price/retailers/savings, pagination, and grouped product rows with price context badges.
Self-Hosted Maps — $0/month Forever
Replaced Mapbox with a fully self-hosted map stack: MapLibre GL JS + Protomaps PMTiles + ESRI satellite imagery. 17GB of USA map tiles served from maps.boisegunclub.com via Nginx + Cloudflare. Five branded time-of-day themes (dawn, day, noon, evening, night) with brand colors infused. Satellite view with state boundary overlays as default. Static map card images rendered server-side via mbgl-renderer. Zero external API dependencies, zero monthly cost.
Photon Geocoding — Replaced Nominatim
Swapped Nominatim (7GB RAM) for Photon geocoder (500MB RAM). Same OpenStreetMap data, fraction of the resources. All 16 geocoding touchpoints updated — frontend autocomplete, Python batch scripts, maintenance tools. Runs at localhost:2322 with full US address coverage.
Valhalla Routing Engine — Self-Hosted Directions
Deployed Valhalla routing engine with the entire US road network (800MB RAM, 87M graph nodes). Provides turn-by-turn directions, isochrone drive-time polygons, distance matrices, and optimized multi-stop routes. Replaced OSRM (which needed 30GB+ RAM) and the public OSRM demo API.
In-App Directions — No More Google Maps Off-Links
Every "Get Directions" button across the site now opens an in-app directions modal instead of sending users to Google Maps. Features: animated route line on satellite map, custom tactical markers (green crosshair origin, orange target destination), estimated arrival time, weather at destination, nearby gun shops along the route, drive/bike/walk mode toggle, share route link, print directions. Works without login — browser geolocation with manual address fallback. 35+ components updated site-wide.
Performance: Sentry & React-Icons Bundle Optimization
Identified @sentry/nextjs as the #1 performance bottleneck (594KB, 30s main thread time). Enabled bundleSizeOptimizations to tree-shake performance monitoring code (GlitchTip only needs error capture). Split react-icons optimizePackageImports into per-library entries for proper tree-shaking. Added @turf/turf to optimization list. Expected vendor bundle reduction from 1.4MB to ~300KB.
Event Pipeline Expansion — 20 Sources, +14,024 Events
Refreshed all 7 working event scrapers (3 weeks stale) and activated 14 dormant scrapers that had never been run. Wired 4 unregistered scrapers (SSUSA, NFAA, SCTP, ISRPA) into the import pipeline. Range calendars alone netted 12,842 new events from 15,248 range websites. Other top contributors: Ducks Unlimited (+246), Register-Ed (+232), CMP Matches (+222), GunShowTrader (+172). Three broken sources identified for repair.
Project Cleanup + CLAUDE.md v9.0
Archived dead code, fixed stale refs, optimized images. Rewrote CLAUDE.md with mandatory audit-first workflow, documented known failure modes (Resend incident, urllib incident, pg_stat incident), and mapped all existing infrastructure.
Production Build Stability
Resolved all pre-existing TypeScript errors blocking production builds. Fixed stale @ts-expect-error directives, added nav-campfire to accentColor union types, removed dead initializedRef reference in map setup hook.
Campfire Page ISR Revalidation
Campfire forum page was statically cached at build time and never revalidating. Added ISR revalidation so new posts appear without a full rebuild.
Event Contact Emails Fixed — Resend API Removed
Discovered 35 real user inquiries silently dropped over 2+ weeks because a previous session built a Resend API integration instead of using the existing Stalwart SMTP sendEmail() function. Ripped out Resend, wired all event contact routes to existing email infrastructure.
Satellite Static Maps + 5 Branded Map Flavors
New static map API generates server-side Mapbox images with GeoJSON pin markers for contact cards and SEO. Five branded map styles (standard, satellite, terrain, dark, light) with React Strict Mode stability fixes for all map components.
Dashboard My Listings + Campfire Comment History
New dropdown on the My Listings dashboard tab for quick switching between claimed businesses. Campfire tab now shows the user's full comment history across all forum threads with timestamps and context.
Community Photos on Detail Pages
Promoted community-submitted photos from a hidden tab to inline display on all business, event, and location detail pages. Photos now appear in the main content flow where users actually see them.
Security Audit — PostgreSQL Exposure + API Hardening
Locked down direct PostgreSQL access (was exposed on port 5433), added API scraping protections with rate limiting and bot detection, tuned CrowdSec rules, and hardened robots.txt to block AI crawlers from scraping business data.
Handbook & Events Page Fixes
Excluded law articles from handbook hero carousel rotation. Fixed events page sections landing — Zustand default viewMode was set to waterfall instead of grid. Removed views badges from all public-facing pages.
Analytics Sync Fix — 60% CTA Events Recovered
Discovered 60% of CTA click events were silently dropped before reaching Umami. Fixed the event pipeline to properly batch and flush analytics events.
GlitchTip Error Triage & Hydration Fix
Audited all 18,000+ production GlitchTip errors. Root-caused hydration mismatches (14K errors) to SSR/client timing on relative timestamps — added suppressHydrationWarning to 35+ render sites. Filtered Sentry streaming errors (272), wrapped sendBeacon for bot/crawler compat, and enhanced Web Vitals CLS attribution with element selectors.
Email Link Login Fix
Fixed auth cookies using sameSite: strict which prevented browsers from sending session cookies when clicking links from emails (claim approvals, verification, welcome emails). Users were silently logged out and redirected to the homepage. Changed to sameSite: lax across all auth endpoints — still blocks CSRF on cross-site POST, but allows top-level GET navigations from emails, Google, and bookmarks.
Email Link Login Fix
Fixed auth cookies using sameSite: strict which prevented browsers from sending session cookies when clicking links from emails (claim approvals, verification, welcome emails). Users were silently logged out and redirected to the homepage. Changed to sameSite: lax across all auth endpoints — still blocks CSRF on cross-site POST, but allows top-level GET navigations from emails, Google, and bookmarks.
Rapid-Fire Approval Race Condition Fix
Fixed a bug where approving multiple moderation queue items quickly caused earlier approvals to silently fail. The undo timer system used a single ref — approving item B cancelled item A's pending API call. Now each approval tracks independently via a Map, so any number of rapid approvals execute correctly.
Rapid-Fire Approval Race Condition Fix
Fixed a bug where approving multiple moderation queue items quickly caused earlier approvals to silently fail. The undo timer system used a single ref — approving item B cancelled item A's pending API call. Now each approval tracks independently via a Map, so any number of rapid approvals execute correctly.
Claims Integrated into Moderation Queue
Business, event, and location claims now appear directly in the moderation queue alongside submissions, photos, reviews, and comments. Dedicated claims review page retired. Claim detail panel shows verification status, uploaded documents, and relationship to entity. Approve/deny triggers email notifications, audit trail, and sets claimed_by on the content record.
Claims Integrated into Moderation Queue
Business, event, and location claims now appear directly in the moderation queue alongside submissions, photos, reviews, and comments. Dedicated claims review page retired. Claim detail panel shows verification status, uploaded documents, and relationship to entity. Approve/deny triggers email notifications, audit trail, and sets claimed_by on the content record.
Author Page + Dashboard Tools
New BGC Staff author page with mesh gradient hero, searchable articles grid, and rewritten voice copy. Dashboard owner tools section added. Business scores page shows coming-soon state. Tab renamed from My Business to My Listings.
Author Page + Dashboard Tools
New BGC Staff author page with mesh gradient hero, searchable articles grid, and rewritten voice copy. Dashboard owner tools section added. Business scores page shows coming-soon state. Tab renamed from My Business to My Listings.
Events Sidebar Stats Fix
Events sidebar stat counts and filter badges now use server-reported totals instead of counting the current page slice, fixing inaccurate numbers on paginated results.
Events Sidebar Stats Fix
Events sidebar stat counts and filter badges now use server-reported totals instead of counting the current page slice, fixing inaccurate numbers on paginated results.
Nested Link Accessibility Fix
Fixed nested anchor tag violations across article cards, event cards, and directory cards. Interactive elements inside links now use button/span wrappers to prevent hydration errors and improve screen reader navigation.
Nested Link Accessibility Fix
Fixed nested anchor tag violations across article cards, event cards, and directory cards. Interactive elements inside links now use button/span wrappers to prevent hydration errors and improve screen reader navigation.
Moderation Pages Polish
Replaced browser alert/confirm dialogs with toast notifications across appeals, auto-mod rules, and scheduled actions pages. Raw fetch calls upgraded to safeFetch for consistent error handling. Filter dropdowns replaced with styled button-based filters matching the queue toolbar pattern. Delete confirmation uses inline buttons instead of browser confirm dialog.
Moderation Pages Polish
Replaced browser alert/confirm dialogs with toast notifications across appeals, auto-mod rules, and scheduled actions pages. Raw fetch calls upgraded to safeFetch for consistent error handling. Filter dropdowns replaced with styled button-based filters matching the queue toolbar pattern. Delete confirmation uses inline buttons instead of browser confirm dialog.
Moderation Queue History + Status Filters
Moderation queue now shows approved, rejected, and revision-needed items via status tabs. Filter counts pull from server-side totals. Game-industry icon set (react-icons/gi) replaces Heroicons across queue, detail panel, and toolbar for a distinctive look.
Moderation Queue History + Status Filters
Moderation queue now shows approved, rejected, and revision-needed items via status tabs. Filter counts pull from server-side totals. Game-industry icon set (react-icons/gi) replaces Heroicons across queue, detail panel, and toolbar for a distinctive look.
Handbook Audit
Fixed article categories, replaced broken/missing featured images, corrected geolocation coordinates for state-specific articles, and set news as the default handbook landing category.
Handbook Audit
Fixed article categories, replaced broken/missing featured images, corrected geolocation coordinates for state-specific articles, and set news as the default handbook landing category.
User Submissions Edit + Appeal
Users can now edit pending submissions and appeal rejected ones from their dashboard. New edit modal pre-fills submission data, appeal modal captures reason text. Status filters and type filters added to submissions tab. Published entity links now use federation-aware state routes.
User Submissions Edit + Appeal
Users can now edit pending submissions and appeal rejected ones from their dashboard. New edit modal pre-fills submission data, appeal modal captures reason text. Status filters and type filters added to submissions tab. Published entity links now use federation-aware state routes.
Moderation Queue Overhaul
Owner moderation queue rebuilt with Zustand state management, extracted components, and keyboard shortcuts. New detail panel with submission preview, submitter context sidebar, photo lightbox, and action modal with reason templates. Bulk actions support approve/reject/needs-revision on multiple items.
Moderation Queue Overhaul
Owner moderation queue rebuilt with Zustand state management, extracted components, and keyboard shortcuts. New detail panel with submission preview, submitter context sidebar, photo lightbox, and action modal with reason templates. Bulk actions support approve/reject/needs-revision on multiple items.
Event State Data Cleanup
Corrected 111 events misassigned to Idaho that belonged to Montana, Oregon, and North Carolina. Great Falls, Helena, Missoula, Kalispell events reassigned to MT. 10 duplicate events archived.
Event State Data Cleanup
Corrected 111 events misassigned to Idaho that belonged to Montana, Oregon, and North Carolina. Great Falls, Helena, Missoula, Kalispell events reassigned to MT. 10 duplicate events archived.
Events Map Shows All Markers
Events page sidebar map and full-screen map view now display all upcoming events for the state, not just the current page of 24. New lightweight /api/events/map-markers endpoint fetches coordinates on demand — zero impact on initial page load payload.
Events Map Shows All Markers
Events page sidebar map and full-screen map view now display all upcoming events for the state, not just the current page of 24. New lightweight /api/events/map-markers endpoint fetches coordinates on demand — zero impact on initial page load payload.
Static Map API + Contact Card Improvements
New server-side static map API route for generating Mapbox map images. Business, event, and location contact cards updated with consistent layout, improved phone/email formatting, and static map integration.
Events Discipline Hero Images
Replaced 8 discipline carousel hero images with editorially curated Wikipedia lead photos. Eliminated military/DoD image dominance across action pistol, shotgun, precision rifle, 3-gun, cowboy action, gun shows, training, and rimfire categories.
Exchange SOLD Listing Overlay
Sold exchange listings now display on the marketplace with a visual SOLD banner overlay instead of being hidden entirely. Buyers can browse completed sales for price research.
Business Image Carousel
New image carousel component on business detail pages. Displays multiple business photos with swipe navigation, fullscreen lightbox, and lazy loading. Supports Wikimedia Commons attribution.
PWA Support + App Icons
Progressive Web App installable with proper manifest, Apple touch icon, 192px and 512px icons, and SVG favicon. Updated manifest.json with theme colors and display standalone mode.
Event Contact Backfill Pipeline
Holy Script #17: extracts contact information (email, phone, website, registration URL) from event descriptions using regex patterns and backfills structured contact fields. Two modes: --extract for analysis and --backfill for applying changes.
Enhanced User Hover Cards
Expanded profile hover cards across Campfire, reviews, and social feeds. Shows avatar, join date, post count, badges, and quick-follow action. Consistent hover behavior across all user mentions.
Community Members Directory
Public members page at /community/members with search, sorting, and profile previews. New API route with pagination and role-based filtering.
Exchange Email Templates
Four transactional email templates for the exchange marketplace: inquiry received, listing expiring, listing published, and listing sold. Matches BGC brand design language with dark headers and warm accent colors.
Dynamic Platform Stats
Replaced all hardcoded stat numbers with real-time database queries. Single source of truth via getPlatformStats() service. Footer, homepage, AI chatbot, map page, and exchange SEO pages all render live counts. Homepage metadata rounds to nearest 5K for SEO.
Article Image Pipeline Consolidation
Consolidated 4 competing article image scripts into Holy Script #15 (article_images.py). Unified modes: --featured, --sections, --brands, --reformat, --promote, --report. Safety gate prevents overwriting existing featured images without explicit --force.
Auto-Create Article Discussion Threads
New cron route automatically creates Campfire discussion threads for handbook articles. Campfire feed default sort changed to newest-first.
Reddit-Style Campfire Reskin
Complete Campfire forum overhaul: flat card design with read-more collapse, threaded comment nesting, persistent left navigation, top bar with category switching, and handbook article carousel. Replaced category grid, topic filters, and legacy layout components.
Footer Mountain Dissolve Transition
Theme-reactive mountain gradient transition between page content and footer. Mountain silhouette adapts to light/dark mode with smooth OKLCH color blending. Events discipline carousel also received major layout improvements.
Platform Changelog
You are here. Public timeline of every major update, derived from 1,776 git commits across 6 months of solo development.
Matrix-Powered Direct Messaging
Replaced the legacy PostgreSQL message system with Matrix (Synapse) for real-time encrypted DMs between buyers and sellers. Admin provisioning, room creation, typing indicators.
Stripe-Style Mobile Navigation
Full-screen drawer with sliding panels and portal escape. Replaced the old hamburger menu with a production-grade mobile nav system.
Homepage Dark Mode Redesign
Overhauled the homepage with dark-mode-first design. New hero, handbook feature cards, footer mountain dissolve, and theme-reactive backgrounds.
BGC Exchange Launches
Free C2C firearms classifieds. No listing fees, no transaction fees. Teal-tabbed UI with sort dropdowns, trust bar, and growth callouts. Full legal framework with ToS v4.0 and Exchange Rules policy.
CrowdSec Security Hardening
Behavioral threat detection with 59 active scenarios parsing Traefik access logs. IP reputation scoring, nftables enforcement, and Cloudflare bouncer integration.
CLS + Loading Skeleton Overhaul
Server-component loading skeletons, font-display:optional, and layout shift elimination across all pillar pages.
February 2026
10 updatesSEO Overhaul
Segmented sitemaps, cache headers, slug enrichment, cross-pillar internal linking, structured JSON-LD, and state-aware breadcrumbs. Indexed pages jumped from 20K to 26,771.
82 National Brand Landing Pages
Sig Sauer, Ruger, Smith & Wesson, Remington, and 78 more. Each with dealer maps, event feeds, and SEO-optimized content.
Helcim Payment Processing
Credit card payments active under MCC 7311. Business subscriptions can now be purchased. Billing dashboard and subscription management.
BGC Business Grade Scoring
Five-pillar scoring system: Data Completeness, Web Presence, Community Engagement, Verification Status, and Content Quality. Every business gets a letter grade A through F.
Claude Code Skills Framework
Built 8 autonomous skills for database queries, pipeline runs, sync guards, enrichment, validation, scraper creation, schema migration, and component generation.
State Validation System
Automatic state correction from ZIP codes via database triggers, Python validators, and TypeScript route guards. Fixed 1,819 misattributed records across all insert paths.
3-Tier Name Normalization
Deterministic cleanup, word segmentation for smushed domains, and AI-assisted fixes. 2,358 business names fixed, 37 unlisted, 3,378 chain stores marked.
58,000+ Events Aggregated
Competitions, classes, shows, and meetups pulled from 96 Python scrapers across national organizations. Backfilled 7,914 event locations via Nominatim reverse geocoding.
Handbook Image Pipeline
Replaced stock photo fallbacks with curated Wikimedia Commons images uploaded to S3. 139 unique images across 152 articles. Killed the Pexels/Pixabay pool system.
Brand Product Image Scrapers
Built 13 manufacturer scrapers pulling 2,579 product images from Savage Arms, S&W, Ruger, Glock, and others. ZIP press kits, CDN tricks, and WordPress lazy-load unwrapping.
January 2026
6 updatesPerformance Optimization Pass
Structured logger conversion, Sentry cleanup, nixpacks heap tuning, and 15 archived unused UI components. 236 commits this month.
Holy 16 Script Consolidation
Consolidated scattered pipeline scripts into 16 canonical entry points under scripts/core/. Clear names, documented usage, archived duplicates.
Aggregator Scraping Network
Built 16 aggregator scrapers: WhereToShoot, SHOT Show, IDPA, CMP, NRA State Associations, GunShowTrader, and more. 16,207 records imported.
9,600+ Shooting Ranges Mapped
Interactive Mapbox-powered range finder covering every state. Filter by type, amenities, and distance. Cross-state redirects for SEO.
National Federation Launch
Expanded from Idaho-only to all 50 states plus DC. Every state gets its own directory, events calendar, and range finder with localized content and cultural voice. Version 2.0.0.
9-Tier Firearms Taxonomy
Comprehensive business classification system covering retail, FFLs, ranges, training, hunting services, archery, tactical, organizations, professional services, online communities, airsoft, and specialty niches.
December 2025
4 updatesHandbook Article Enhancements
Rich visual hierarchy in article markdown, discipline landing pages, TLDR callouts, and ticker improvements.
Unified Location System
Deduplicated shooting locations, renamed foreign keys, synced schema between businesses and locations. Smart geocoding scripts for batch coordinate resolution.
AI Business Enrichment Pipeline
Built a 5-step pipeline using the Anthropic Batch API: discover, validate, scrape, validate again, enrich. 50% cost savings over synchronous calls. The backbone of data quality.
Content Enrichment + SEO Generation
AI-generated business descriptions, meta titles, and structured markdown with callouts. Listing quality scoring for directory prioritization.
November 2025
6 updatesESLint + Production Hardening
Major ESLint cleanup session, production-ready build config, and GlitchTip error tracking integration. 606 commits this month.
Public Launch
Boise Gun Club goes live on November 24, 2025. Idaho-focused directory, events calendar, range finder, and handbook. The starting line, not the finish.
Cap.js CAPTCHA Integration
Production CAPTCHA on all forms with dev bypass mode. Blocks bot submissions without third-party dependency.
Business Claims + Owner Dashboard
Business owners can claim their listing, manage details, upload photos, and track analytics. Full owner moderation panel.
Mapbox Unified Map System
Consolidated all map components into a single architecture with core, variants, and controls. Geolocation, distance rings, traffic layers, and cluster rendering.
Playwright E2E Test Suite
Mobile-first end-to-end tests across 6 device profiles. Full user journey coverage for registration, directory, events, and ranges.
October 2025
4 updatesBrass Economy + Gamification
Daily login rewards, brass earned notifications, and the foundation for community engagement scoring.
Handbook Wiki System
Encyclopedic firearms reference with infoboxes, sticky table of contents, TLDR callouts, and structured markdown rendering. BBC-style news grid on the dashboard.
Custom JWT Auth System
Migrated from NextAuth to a custom JWT authentication system. Proper token handling, secure cookie management, and Zustand auth store.
Design System Overhaul
Massive typography standardization, Tailwind semantic utility migration, and color consolidation. Eliminated 122 unused files, reducing codebase by 31%. 537 commits in one month.
September 2025
3 updatesArchitecture Transformation
Consolidated 74 bloated components into a unified design system. CVA button variants, atomic component hierarchy, enterprise animation hooks. 83 commits in 3 weeks.
Drizzle ORM + Data Pipeline v2
Integrated Drizzle ORM with type-safe schema, TanStack Query for client caching, Zustand stores for filters, and Zod validation. Built the first data pipeline for event ingestion.
Project Genesis
Fresh codebase initialized. Next.js 15, React 19, Tailwind v4, Drizzle ORM, Zustand, Framer Motion. The foundation for everything that follows.
Boise Gun Club has been in active development since July 31, 2025.
This log is generated from 9,706+ git commits across 11 months of development.