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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
Manual migration adds pg_trgm GIN indexes on listings to speed up ILIKE %x% searches. Ships as CONCURRENTLY-safe standalone SQL.
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.
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).
New scripts/discovery/probe_manufacturers.py — quick reachability check across 21 manufacturer catalogs before launching full discovery runs.
New scripts/maintenance/extract_specs_from_title_handle.py — recovers ammo specs from existing product text without re-scraping raw sources.
Search page now renders market products (11M+) with dedicated card, filter pill, pagination. Previously dropped silently.
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.
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.
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.
Market search now paginates against Typesense directly, showing true total count instead of capped aggregator limit.
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.
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.
Added firearms safety net (complete-gun keyword + gauge/caliber → firearms) and fixed holsters regex matching "owb" inside "cowboy". Recovered 28,747 misclassified firearms.
Range-calendar + gunshowtrader aggregator reruns after idle-in-transaction bug fix. Event total 107,501 → 112,108.
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".
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.
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.
Market category pagination now renders <a href> tags with rel=prev/next instead of onClick-only buttons — Googlebot can discover all paginated product pages.
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.
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.
Thumbs up/down on wall posts now persists after page reload — feed API was missing auth cookie on fetch
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.
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.
Menu items no longer flash solid slate-blue on hover — uses subtle muted background instead
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.
Rich text editor no longer shows heavy gray border ring when focused — cleaner, quieter interaction
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.
Campfire and Night Ops theme names were swapped in the user menu — now match their actual appearance
Emoji reaction trigger no longer has a prominent border at rest — matches sibling action buttons
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.
Product classifier now uses UPC lookback, fastText local ML (50K/sec), and Haiku API with signal cross-validation — rescued 292K products from other/unclassified.
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
Business owners can now update their address directly from the dashboard. Coordinates auto-geocode via Photon on save.
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.
Social share cards now feature seeded procedural mountain ridges with pillar-colored atmospheric glow behind the peaks.
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.
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.
Added product count to the sunrise hero stat grid — the market is the growth engine and now leads the stats bar
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.
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.
Events, directory, handbook, ranges, exchange, and campfire pages now auto-select their own branded OG image for social sharing.
Homepage hero rebuilt with procedural mountain silhouettes, stat bar, and section nav pills.
Scheduled 2x daily (7am + 5pm UTC) via n8n. SearXNG discovery in n8n, Claude writing via cron webhook. Fully automated content pipeline.
Added pillar-colored gradient tint to the Handbook hero carousel, giving it the same identity moment that Events, Directory, and Ranges have on arrival.
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.
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.
Campfire topics now emit structured data for Google rich results.
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.
New NetSuite SuiteCommerce API scraper imported 19,630 products with full specs, UPC, and stock data.
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.
New Playwright test that compares loading skeleton bounding boxes against loaded page layout to catch structural divergence before deploy.
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.
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.
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
Added caliber distribution and product categories sidebar cards to national brand pages with new market queries
Replaced expensive COUNT queries and GROUP BY aggregations with pre-computed materialized views (mv_market_stats, mv_market_category_stats, mv_homepage_deals)
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.
Rewrote root, market, and campfire loading.tsx skeletons to match actual page layouts with proper section structure, grid columns, and sidebar placement
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.
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.
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.
Rewrote article JSON parsing to handle Claude's unescaped content field (newlines, tabs, quotes). Structural boundary detection instead of regex.
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.
v2.FEATURES.FILES — features cumulative since v2.0, files is a lifetime odometer of total surface area touched. Never resets.
Subcategory sections (Compound Bows, Riflescopes, etc.) now filter by product_type instead of title text search. Shows actual bows, not bow cases.
Great Deal badge now requires 2+ retailers carrying the product AND 20%+ savings vs group average. Was previously flagging 15% of firearms.
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.
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.
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.
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.
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.
348 new brand aliases added from unknown-brand audit. Import pipeline now extracts brand from product title when WooCommerce brand field is empty.
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.
import_market.py --matviews-only flag for refreshing materialized views without running a full import cycle.
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.
Facets and products now fetched in parallel via Promise.all instead of sequentially. Single Typesense health check per request. ~40-50%% faster filter responses.
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.
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.
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.
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.
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.
237K+ products enriched with manufacturer-grade specs from 29 PDF catalogs. Spec coverage: firearms 68%, ammo 33%, parts 58%, optics 43%.
Event organizer emails and phone numbers no longer displayed publicly. Contact goes through our form and routes server-side. Prevents scraping.
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.
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.
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.
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).
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.
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.
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.
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.
Amazon-style filter sidebar for all market category pages with sticky positioning and mobile drawer.
Self-hosted static map generation via mbgl-renderer replacing Mapbox Static Images API.
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.
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.
RTS-style diamond bracket markers replace plain circles. Canvas-generated at runtime, section-colored per variant (directory/events/ranges/market).
BLM, USFS, NPS, BIA, DOD boundaries as dashed outlines with agency labels. No color fill — elevation tint shows through. Toggleable via control panel.
All map overlays render via React portal to document.body, escaping sticky navbar stacking context. z-index 9999 now works correctly.
Sidebar maps get a small expand button. Click to go fullscreen with full controls. No controls visible in sidebar view.
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.
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.
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.
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.
Expanded PMTiles coverage from CONUS-only to all US territories. Alaska, Hawaii, Puerto Rico, and US Virgin Islands now have full vector tile data.
Deduplicated 15,637 raw scrape files (320GB), offloaded 68GB migration archives to S3, deleted old project copies. Disk usage dropped from 96% to 56%.
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.
Removed road casing layers (dark border + white center AA artifacts) and county boundary clutter. Highways keep casings for distance readability, state borders stay.
Zoom-based water color shift (deep teal at regional → lighter at street level) plus shoreline edge strokes on all water polygons.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
New /tmp-map page with layer debugger — toggle every map layer independently, sliders for terrain exaggeration and pitch, all map component variants displayed.
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.
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.
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.
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.
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.
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.
Ranges page now filters by Trap & Skeet, Pistol, Rifle, Archery, and Indoor based on actual facility data — not just location type.
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.
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 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.
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.
Users can now attach images directly in comments. Images are uploaded, sanitized, and rendered inline.
Renamed StockpileStats/StockpileCaliberOption interfaces and functions to Market equivalents. Removed unused getActiveExchangeDeals and image search service dead code.
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.
Replaced hardcoded 51 state count with FEDERATION_STATES.length in 3 components. Added suppressHydrationWarning + timeZone UTC to date rendering in 4 client components.
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).
Market stats on homepage hero (Products count), Compare Prices nav pill, and market data in /api/stats endpoint.
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.
Unified ammo filter sidebar with search include/exclude, sort pills, caliber/brand dropdowns, price checkboxes — all URL-param driven for server-side filtering.
Business hero card map was missing the MapLibre CSS import, causing the location pin to be invisible despite being in the DOM.
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.
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.
Removed fake sponsored badge system that flagged unclaimed enriched listings as sponsored. Real sponsor infrastructure (is_sponsored, tiers) preserved for future paying customers.
Comparison table now shows retailer names with favicons, data freshness timestamps, round counts, and value badges. Single-retailer products shown with muted styling.
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.
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.
Consolidated marketplace → market naming across all routes, queries, types, and components. Cleaned up dead code and duplicate files.
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.
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.
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.
Merged fragmented event categories into clean buckets (competition, show, training, community, conservation) while preserving granular event_type for sub-filtering.
Import pipeline was using hardcoded March 17 date for all products. Now uses current datetime. Next scraper run will show fresh timestamps.
Ran website discovery pipeline on 1,937 ranges without websites. Found 58 via fuzzy matching and 92 via manual review of search results.
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.
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.
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.
Regex-based classification assigned category and event_type to all remaining null events. Zero uncategorized events remain.
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.
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.
Server-rendered business detail hero with client islands. Eliminated layout shift and reduced TTI on directory detail pages.
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.
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.
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.
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 now includes stockpile products, national brand pages, organization pages, and marketplace category pages. Segmented pagination for 330k+ product URLs.
New generic_product_scraper.py for non-Shopify/WC platforms. 600+ WooCommerce retailers added. Sitemap-based product discovery pipeline.
Reordered JSON recovery stages — control character fixing now runs before brace matching, fixing 15-20% of truncated article parse failures.
JSON-LD for national brands and organizations moved from metadata.other to proper Script tags in page components, fixing Google structured data validation.
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.
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).
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.
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.
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.
Successful scraper fix recovered 50+ previously failing retailers, bringing total indexed products from 168K to 338K across 75 Shopify stores.
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.
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.
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.
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.
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.
MUTCD-accurate highway shields (Interstate blue/red, US Route black/white) via @americana/maplibre-shield-generator. Renders dynamically at runtime from ShieldJSON definitions.
Imperial scale bar on all maps, cooperative gestures to prevent accidental zoom, hover highlighting on map pins with GPU-accelerated feature state.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
/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.
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.
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.
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.
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.
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.
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.
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.
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 forum page was statically cached at build time and never revalidating. Added ISR revalidation so new posts appear without a full rebuild.
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.
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.
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.
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.
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.
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.
Discovered 60% of CTA click events were silently dropped before reaching Umami. Fixed the event pipeline to properly batch and flush analytics events.
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.
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.
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.
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.
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.
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.
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.
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.
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 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 stat counts and filter badges now use server-reported totals instead of counting the current page slice, fixing inaccurate numbers on paginated results.
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.
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.
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.
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 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 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.
Fixed article categories, replaced broken/missing featured images, corrected geolocation coordinates for state-specific articles, and set news as the default handbook landing category.
Fixed article categories, replaced broken/missing featured images, corrected geolocation coordinates for state-specific articles, and set news as the default handbook landing category.
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.
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.
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.
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.
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.
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 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 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.
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.
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.
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.
New image carousel component on business detail pages. Displays multiple business photos with swipe navigation, fullscreen lightbox, and lazy loading. Supports Wikimedia Commons attribution.
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.
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.
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.
Public members page at /community/members with search, sorting, and profile previews. New API route with pagination and role-based filtering.
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.
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.
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.
New cron route automatically creates Campfire discussion threads for handbook articles. Campfire feed default sort changed to newest-first.
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.
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.
You are here. Public timeline of every major update, derived from 1,776 git commits across 6 months of solo development.
Replaced the legacy PostgreSQL message system with Matrix (Synapse) for real-time encrypted DMs between buyers and sellers. Admin provisioning, room creation, typing indicators.
Full-screen drawer with sliding panels and portal escape. Replaced the old hamburger menu with a production-grade mobile nav system.
Overhauled the homepage with dark-mode-first design. New hero, handbook feature cards, footer mountain dissolve, and theme-reactive backgrounds.
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.
Behavioral threat detection with 59 active scenarios parsing Traefik access logs. IP reputation scoring, nftables enforcement, and Cloudflare bouncer integration.
Server-component loading skeletons, font-display:optional, and layout shift elimination across all pillar pages.
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.
Sig Sauer, Ruger, Smith & Wesson, Remington, and 78 more. Each with dealer maps, event feeds, and SEO-optimized content.
Credit card payments active under MCC 7311. Business subscriptions can now be purchased. Billing dashboard and subscription management.
Five-pillar scoring system: Data Completeness, Web Presence, Community Engagement, Verification Status, and Content Quality. Every business gets a letter grade A through F.
Built 8 autonomous skills for database queries, pipeline runs, sync guards, enrichment, validation, scraper creation, schema migration, and component generation.
Automatic state correction from ZIP codes via database triggers, Python validators, and TypeScript route guards. Fixed 1,819 misattributed records across all insert paths.
Deterministic cleanup, word segmentation for smushed domains, and AI-assisted fixes. 2,358 business names fixed, 37 unlisted, 3,378 chain stores marked.
Competitions, classes, shows, and meetups pulled from 96 Python scrapers across national organizations. Backfilled 7,914 event locations via Nominatim reverse geocoding.
Replaced stock photo fallbacks with curated Wikimedia Commons images uploaded to S3. 139 unique images across 152 articles. Killed the Pexels/Pixabay pool system.
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.
Structured logger conversion, Sentry cleanup, nixpacks heap tuning, and 15 archived unused UI components. 236 commits this month.
Consolidated scattered pipeline scripts into 16 canonical entry points under scripts/core/. Clear names, documented usage, archived duplicates.
Built 16 aggregator scrapers: WhereToShoot, SHOT Show, IDPA, CMP, NRA State Associations, GunShowTrader, and more. 16,207 records imported.
Interactive Mapbox-powered range finder covering every state. Filter by type, amenities, and distance. Cross-state redirects for SEO.
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.
Comprehensive business classification system covering retail, FFLs, ranges, training, hunting services, archery, tactical, organizations, professional services, online communities, airsoft, and specialty niches.
Rich visual hierarchy in article markdown, discipline landing pages, TLDR callouts, and ticker improvements.
Deduplicated shooting locations, renamed foreign keys, synced schema between businesses and locations. Smart geocoding scripts for batch coordinate resolution.
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.
AI-generated business descriptions, meta titles, and structured markdown with callouts. Listing quality scoring for directory prioritization.
Major ESLint cleanup session, production-ready build config, and GlitchTip error tracking integration. 606 commits this month.
Boise Gun Club goes live on November 24, 2025. Idaho-focused directory, events calendar, range finder, and handbook. The starting line, not the finish.
Production CAPTCHA on all forms with dev bypass mode. Blocks bot submissions without third-party dependency.
Business owners can claim their listing, manage details, upload photos, and track analytics. Full owner moderation panel.
Consolidated all map components into a single architecture with core, variants, and controls. Geolocation, distance rings, traffic layers, and cluster rendering.
Mobile-first end-to-end tests across 6 device profiles. Full user journey coverage for registration, directory, events, and ranges.
Daily login rewards, brass earned notifications, and the foundation for community engagement scoring.
Encyclopedic firearms reference with infoboxes, sticky table of contents, TLDR callouts, and structured markdown rendering. BBC-style news grid on the dashboard.
Migrated from NextAuth to a custom JWT authentication system. Proper token handling, secure cookie management, and Zustand auth store.
Massive typography standardization, Tailwind semantic utility migration, and color consolidation. Eliminated 122 unused files, reducing codebase by 31%. 537 commits in one month.
Consolidated 74 bloated components into a unified design system. CVA button variants, atomic component hierarchy, enterprise animation hooks. 83 commits in 3 weeks.
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.
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 8 months of development.