113 Commits

Author SHA1 Message Date
root b6df4dbb92 Snapshot: MLS sync fixes, image refresh, plugin/theme updates
MLS plugin fixes from this session:
- Fix silent insert failures: location column NOT NULL was rejecting wpdb->insert calls,
  causing ~18k new properties since Dec 2025 to be lost. Inserts now build raw SQL
  with ST_PointFromText so the spatial column is populated atomically.
- Auto-refresh expired media URLs in MLS_Media_Handler::fetch_and_cache(), guarded by
  a property-level GET_LOCK so concurrent fetches share one API refresh.
- Normalize WP_Error to null in mls_get_property_image() so callers can rely on the
  documented string|null contract.
- Support comma-separated property_type filters in MLS_Query and MLS_Cluster so the
  homepage "View All Commercial" link (?property_type=Commercial+Sale,Land,Farm)
  actually filters correctly.
- Incremental sync now looks back 10 minutes past the latest modification timestamp
  as a safety margin against missed records.
- Smart sync exits silently (info-level, not warning) when a full sync is in progress.

Operational:
- New cron: weekly full sync Sundays at 3 AM (/usr/local/bin/mls-full-sync).
- New cron: hourly 2GB cap on mls-thumbnails/ and cache/transformed-images/
  (/usr/local/bin/mls-image-cache-cap).
- Logrotate config for wp-content/debug.log (2-day retention, daily rotation,
  delaycompress).

Repo policy:
- CLAUDE.md updated with explicit "commit everything except build artifacts" policy.
- .gitignore: untrack runtime image caches and debug.log rotations.

Other modifications in this snapshot are pre-existing in-flight theme/plugin/db_content_updates work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:32:23 +00:00
root 57b752f54e Manual property enhancements: MLS status sync, agent clone, description formatting
- Manual properties linked to MLS now inherit status (Active/Pending/Closed) and
  days_on_market from the MLS listing dynamically
- Properties not in MLS default to Closed status
- Clone feature now auto-populates listing agent by matching MLS ID to Agent CPT
- Description formatter detects embedded headers (unpunctuated text after sentences)
  and splits them into separate paragraphs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 21:28:44 +00:00
Hanson.xyz Dev c2d5b2248d Add WebP conversion and garbage collection to MLS plugin
Image handling improvements:
- Convert PNG and images >500KB to WebP format
- Resize images wider than 1600px maintaining aspect ratio
- Check for .webp version before falling back to original
- WebP quality set to 80 (equivalent to JPEG 90%)

Garbage collection for disk space management:
- New MLS_Garbage_Collector class runs after each sync
- Only active when MLS_GC_DISK_THRESHOLD defined in wp-config
- Deletes image directories older than 24 hours, oldest first
- Stops when free space reaches 5GB or 2GB deleted per run
- Protects recently accessed images from deletion

Documentation:
- Added Garbage Collection section to README
- Updated Features list and File Structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 20:48:57 -06:00
Hanson.xyz Dev acc8ac87a0 wip 2026-01-04 17:50:08 -06:00
Hanson.xyz Dev 7e45ce0756 Fix homeprozAjax not defined on properties page
Update localization condition to check for properties page template
instead of the removed property post type archive.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:14:21 -06:00
Hanson.xyz Dev 9bf45416d5 Add properties page template to replace CPT archive
Creates page-properties.php template and Properties page to handle
/properties/ URL after removing the property post type.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:13:01 -06:00
Hanson.xyz Dev 361007d36b Add homepage section ordering and switch to MLS-only data source
Homepage Improvements:
- Add Section Order tab with enable/disable toggles and order numbers
- Support up to 2 custom WYSIWYG content areas with titles
- All 4 sections (Service Cards, Property Types, Custom 1, Custom 2)
  can be reordered and toggled independently

Property Type Boxes:
- Convert to ACF repeater (remove MLS count queries)
- Add red accent color to icons
- Center-align icons, increase title size, add spacing

MLS Data Source:
- Remove property CPT and taxonomies (MLS Editor is now source of truth)
- Commercial section now pulls from MLS with priority:
  1. Featured (favorited) commercial properties
  2. HomeProz-listed commercial properties
  3. Random commercial/land/farm properties
- Featured Homes section updated to same priority order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 13:00:33 -06:00
Hanson.xyz Dev cf56d57225 Enable drag-drop sorting for Agents post type
- Add page-attributes support and make agent CPT hierarchical
- Add Simple Page Ordering plugin filter for agent sorting
- Update agent queries in archive-agent, page-about, and page-join
  to use menu_order instead of ACF agent_order field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 11:51:58 -06:00
Hanson.xyz Dev ce02635b57 Skip initial property list render when map will reposition
On fresh page load without URL state or filters, the map repositions
to fit all properties. Previously, the server-rendered property list
would briefly show before being replaced by viewport-filtered results.

Now we immediately show a spinner when we know the map will reposition,
preventing the flash of unfiltered content and unnecessary rendering.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 21:22:20 -06:00
Hanson.xyz Dev eed01f2e04 Switch to 100% server-side map clustering
Removes client-side Leaflet MarkerCluster library in favor of
server-side clustering at all zoom levels:
- Zoom 1-8: Density dots
- Zoom 9-15: Server-generated numbered clusters
- Zoom 16+: Individual property markers

This prevents the visual issue where server-returned clusters were
being re-clustered by the client into a single merged marker.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 03:16:16 -06:00
Hanson.xyz Dev f2a9b28ac2 Fix city/zip filtering and improve map hover behavior
MLS Query Changes:
- Use exact city/postal_code matching instead of radius search
- Fixes city filter returning 1700+ results instead of 97 for Ramsey

Cluster Endpoint:
- Parse "City, SS" format to extract city name before querying
- Fixes pins not showing when city filter applied

Property Filters JS:
- Always fit map bounds when filter changes (not just on no intersection)
- Fit bounds on initial page load when URL has filters
- Show temporary hover pin when marker is clustered or outside viewport
- Uses markerCluster.getVisibleParent() to detect clustered markers

Property Results:
- Add zip code parameter handling for URL filters

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 02:27:54 -06:00
Hanson.xyz Dev 5ca2e29c72 Add ghost text autocomplete location search to homepage hero
Replaces community dropdown with text input supporting city/ZIP search:
- Ghost text autocomplete shows inline suggestion as user types
- Tab to accept, auto-fill on blur, Enter uses partial match
- Geolocation button for "Use My Location" searches
- AJAX endpoint returns MN/IA cities and zipcodes with 1-hour cache
- MLS query now supports lat/lng/radius for distance-based filtering
- Updated Census Bureau 2023 Gazetteer data (32,329 US cities)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 00:50:45 -06:00
Hanson.xyz Dev 183e1b92c9 Add 6 reusable page templates with ACF integration
Introduces layout-focused templates for marketing pages:
- Content with Sidebar: 70/30 grid with callout boxes
- Alternating Blocks: Zigzag image/text sections
- Service Detail: Hero + features grid + FAQ accordion
- Card Grid: Configurable 2/3/4 column card layouts
- Long-Form Article: Clean reading layout with related links
- Landing Page: Conversion-focused with benefits and testimonial

Each template has corresponding ACF field groups for content
management. Sample pages created under /page-template-examples/.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 00:50:33 -06:00
Hanson.xyz Dev b8d9c8aee7 Add sticky filter improvements and unified filter input styling
- Change sticky filter visibility check to use reset button position
- Add reset button to sticky filter (centered, below filter grid)
- Reset filters now resets map to initial position if no results
- Unify select/input styling: same height, black background, consistent borders
2025-12-17 17:36:38 -06:00
Hanson.xyz Dev 564d556a8c Add US geo data tables, filter bounds API, and URL hash state management
- Add mls_geo_cities and mls_geo_zipcodes tables with 29,880 cities and 33,144 zip codes
- Add get_filter_bounds() method to reposition map when filters don't intersect current view
- Move all URL state (filters, page, scroll, map position) to hash to avoid WordPress 404s
- Add filter bounds AJAX endpoint for map repositioning on filter change

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 15:07:33 -06:00
Hanson.xyz Dev 5522d18ada Improve pin click behavior: scroll to card if exists, pan map if not
- When clicking a pin, check if property card exists in loaded results
- If card exists: scroll to it smoothly and highlight (no map pan)
- If card doesn't exist: pan map to center on pin, reload results
- Add isCardScroll flag to ignore stray map events during card scroll
- Skip scroll reset in InfiniteScroll when pin-triggered pan
2025-12-17 03:53:52 -06:00
Hanson.xyz Dev 63b8fec917 Add sessionStorage caching for AJAX requests and URL state restoration
- Add unified AjaxCache for all AJAX responses (5 min expiry)
- Cache key based on request params (minus nonce), coordinates rounded to 4 decimals
- Clean expired cache entries on page load
- URL hash stores page, scroll, lat/lng/zoom for state restoration
- Bulk load pages in parallel on restore, use cache when available
- Add min-height 100vh to property results in map view
- Change all scroll animations to instant
2025-12-17 03:42:49 -06:00
Hanson.xyz Dev 1c81fd6766 Add scroll-triggered image loading with 1000px distance limit
- Trigger image loader on scroll events (debounced 50ms)
- Only load images within 1000px of viewport edges
- Images further away wait until user scrolls closer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 02:47:47 -06:00
Hanson.xyz Dev 51a5c3e166 Add sequential image loader with viewport prioritization
- Load property card images 2 at a time instead of all at once
- Prioritize images in viewport, then by distance from viewport
- Single execution guard prevents concurrent loading runs
- Gracefully handles DOM removal (cleared grid aborts pending loads)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 02:45:10 -06:00
Hanson.xyz Dev dfad0f57e6 Add hover pins for clustered properties, disable grouping under 30 markers
- Add data-lat/data-lng attributes to MLS property cards
- Create temporary highlighted pin on card hover when marker is clustered
- Show individual pins (no grouping) when <= 30 properties in viewport
- Add markerLayer for unclustered markers to bypass client-side clustering
- Show loading spinner immediately on map move to abort image loads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 02:31:23 -06:00
Hanson.xyz Dev 8cd630593d Fix scroll position on map viewport change
Scroll to bottom of .property-filters minus masthead height when
map viewport changes and results refresh. Only scrolls upward,
never down. Uses instant scroll behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 02:13:25 -06:00
Hanson.xyz Dev ecc182ebf9 Rewrite infinite scroll to use card-based virtual scrolling
- Measure page positions via first card's getBoundingClientRect
  (page wrappers use display:contents so have no box)
- Convert individual cards to placeholders when scrolling away
  (store HTML in jQuery data, set fixed dimensions, empty content)
- Restore cards from placeholders when scrolling back
- Calculate current page based on which page's top is closest
  to viewport bottom without being below it
- DLP (Desired Loaded Pages) = [CP-2, CP-1, CP, CP+1, CP+2]
- Load one page at a time via AJAX

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-17 02:02:35 -06:00
Hanson.xyz Dev 02cc7f2dfb Remove Search button from filter bar, keep only Reset 2025-12-16 15:20:40 -06:00
Hanson.xyz Dev 8aeb33ed2c Add sticky filter form below map on desktop
- Create property-filters-sticky.php with compact filter layout
- Add StickyFilters JS module with IntersectionObserver
- Show sticky filter with 200ms fade when main filter scrolls out of view
- Hide sticky filter instantly when main filter scrolls back into view
- Bidirectional sync between main and sticky filter forms
- Changes in either form trigger the same filterProperties() call
- Desktop map view only (>= 1024px)
2025-12-16 14:55:43 -06:00
Hanson.xyz Dev 9228a1f1ea Enable infinite scroll on mobile view
- Remove desktop-only restriction from InfiniteScroll.init()
- Auto-detect container: .property-list-container (desktop map) or #property-results (mobile/grid)
- Hide pagination on all screen sizes when infinite scroll is enabled
- Reinitialize infinite scroll after all AJAX filter loads (not just desktop)
2025-12-16 14:38:21 -06:00
Hanson.xyz Dev bc39aa19dc Add 500px prefetch zone for earlier infinite scroll loading
- Add 500px bottom padding when more pages exist (hidden on last page)
- Trigger next page load when viewport enters last 500px of content or padding
- Add checkImmediateLoad() to chain page loads if viewport still in zone
- Runs immediately after page 1 and after each subsequent page loads
- Enables rapid sequential loading when user scrolls far or has tall viewport
2025-12-16 14:27:17 -06:00
Hanson.xyz Dev 53d3c41917 Change infinite scroll to use window scroll instead of container
- Remove overflow-y:auto and max-height constraints from property list
- Use viewport-based IntersectionObserver (root: null) instead of container
- Track and maintain max grid height to prevent layout shift on scroll up
- Clear max height only on filter/map change (not on normal scroll)
- Update scroll anchor methods to use window.scrollY/scrollBy
- Mobile continues to use pagination (desktop only infinite scroll)
2025-12-16 14:21:29 -06:00
Hanson.xyz Dev 761384ee1b Add request queue to prevent map/filter race conditions
- Add RequestQueue module with 200ms debounce and request cancellation
- Queue cluster requests to abort pending when new viewport/filter changes
- Queue property list requests to prevent stale data rendering
- Track request IDs to discard responses from cancelled requests
- Error handler ignores aborted requests (intentional cancellation)

Fixes issue where changing filters then zooming map before load complete
would render pins from previous filter state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 14:02:53 -06:00
Hanson.xyz Dev cf181d01b3 Implement lower-priority UX enhancements
- Add subtle noise texture overlay for luxury feel (body::before)
- Optimize mobile homepage: service cards 2-up layout, reduced padding/sizes
- Add agent contact details section above gallery/listings on profile pages
- Create Join Our Team recruitment page with benefits grid and team preview
- Enhance Buyers/Sellers guide cards with visual section, gradient backgrounds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 13:45:46 -06:00
Hanson.xyz Dev 0487bd1dcf Implement high priority features: filters, property type boxes, email routing
- Expanded search filters: MLS property types dropdown, MLS cities (50+ listings), zip code text input
- Property type showcase boxes: 5-category grid on homepage with icons, descriptions, counts
- Multi-recipient email: Primary to office@, CC to info@, sender confirmation receipt enabled
- Added helper functions: homeproz_get_mls_property_types(), homeproz_get_mls_cities()
- Updated FEATURES_PENDING with completion status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 13:21:23 -06:00
Hanson.xyz Dev 07a8d1756e Implement launch blockers and MLS state filter
- Add MLS state filter for MN/IA only queries
- Add property inquiry form auto-population with read-only display
- Update broker info and office hours in footer
- Remove Bridge Realty text from about page
- Update service area to Minnesota and Iowa
- Add HomeProz listing identification (is_homeproz column)
- Add dynamic featured listings on front page
- Add gallery thumbnail preloading and loading spinners
- Update FEATURES_PENDING with completion status

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 13:07:12 -06:00
Hanson.xyz Dev 15449b9131 Add single MLS property view and image security improvements
- Add single-property-mls.php template with full gallery support
- Route /properties/?listing=XXX to single property view
- Add HMAC-signed URLs for image endpoint (bot protection)
- Add MySQL advisory lock for image downloads (prevent stampede)
- Add infinite scroll module for property list (desktop map view)
- Load card images immediately on DOM ready (no scroll detection)
- Add cards_only AJAX parameter for infinite scroll

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 10:43:04 -06:00
Hanson.xyz Dev acd606bb03 Sync property list with map viewport
Major changes:
- Property list now updates when map pans/zooms
- Properties sorted by distance from map center (closest first)
- Shows "X properties in view" when viewport filtering active
- Min 30 properties required before grouping kicks in
- Added rule to CLAUDE.md: no commits unless asked

Backend:
- MLS_Query: Added bounds filtering and distance-based sorting
- AJAX handler: Accepts bounds/center, sorts by distance when provided

Frontend:
- Map move triggers property list refresh with same viewport
- Loop prevention flag to avoid map->filter->map recursion
- Resets to page 1 when viewport changes

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 01:27:44 -06:00
Hanson.xyz Dev 9337a3cbc7 Start numbered clusters at zoom 9 instead of 12
- Zoom 1-5: Dense density dots
- Zoom 6-8: Normal density dots
- Zoom 9-15: Numbered clusters
- Zoom 16+: Individual markers

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 01:07:26 -06:00
Hanson.xyz Dev 8c44e07bc4 Use zoom-relative density coloring with warm palette
- Density thresholds now scale with zoom level:
  zoom 3: ~600 = high, zoom 7: ~150 = high, zoom 11: ~40 = high
- Warm color palette: green -> lime -> gold -> amber -> burnt orange
- 20% transparency on all dots for softer appearance
- Softer borders and shadows on dots

This makes the same property count appear as "low density" when
zoomed out (seeing 25k properties) but "high density" when zoomed
in (seeing only nearby properties).

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 01:04:16 -06:00
Hanson.xyz Dev 7c3c322449 Replace heatmap with density dots for all zoom levels
Remove Leaflet.heat in favor of consistent density dot visualization:
- Zoom 1-5: Density dots with 40% more density (24px spacing)
- Zoom 6-11: Density dots with normal spacing (40px)
- Zoom 12-15: Numbered cluster circles
- Zoom 16+: Individual property markers

Density dots provide clearer visualization than heatmap blobs for
high-density property data.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:58:59 -06:00
Hanson.xyz Dev 93d5b01111 Add tiered map visualization based on zoom level
- Zoom 3-7: Heatmap overlay showing property density
- Zoom 8-11: Density dots (small colored circles without numbers)
- Zoom 12-15: Numbered cluster circles
- Zoom 16+: Individual property markers

Backend returns different data types (heatmap/density/clusters/markers)
based on zoom level. Frontend uses Leaflet.heat for heatmap and custom
divIcons for density dots with color gradient based on count.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:53:21 -06:00
Hanson.xyz Dev 1c728ec60e Fix clustering density with pixel-based grid calculation
Replace geohash precision mapping with dynamic grid sizing based on
target pixel spacing (60px between cluster centers). Uses Leaflet/OSM
tile math to calculate degrees-per-pixel at each zoom level, adjusted
for Mercator projection at the viewport's center latitude.

At zoom 7, this gives ~52km cells and ~150 clusters statewide,
properly separating Minneapolis from St. Cloud.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:31:11 -06:00
Hanson.xyz Dev ae0fc65e4e Fix: Remove paged from formData to prevent ?paged= in URL
The formData object was getting paged added, which then got included
in the URL query params by updateUrl(). Now paged is only sent in
the AJAX POST data, not added to formData.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:19:39 -06:00
Hanson.xyz Dev fe29eb74c4 Fix server-rendered pagination links to use hash format
Update paginate_links() in both property-results.php and ajax-handlers.php
to generate #page=N links instead of ?paged=N query parameters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:10:29 -06:00
Hanson.xyz Dev b7eb0de882 Use hash values for AJAX pagination instead of query params
Prevents conflicts with WordPress server-side pagination handling.
URLs now use #page=2 format instead of ?paged=2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:07:20 -06:00
Hanson.xyz Dev 1862cef42a Add server-side clustering for map with 30k+ properties
- Remove 1000 property limit from count display
- Add MLS_Cluster class for geohash-based server-side clustering
- Add AJAX endpoint for dynamic cluster loading based on viewport/zoom
- Update property-results.php and ajax-handlers.php to use efficient counting
- Update map JavaScript to fetch clusters dynamically as user pans/zooms
- Server returns clusters at low zoom, individual markers at high zoom
- Fixes property count showing 1000 instead of actual ~30k properties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 00:04:22 -06:00
Hanson.xyz Dev 30eb593020 Add loading spinner for property card images
- Show spinning loader while images load
- Lazy load images as cards enter viewport (with 200px buffer)
- Use data-bg attribute to defer background-image loading
- MutationObserver detects AJAX-loaded content
- Spinner hides when image loads or on error
- Fallback to placeholder on load error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 23:48:59 -06:00
Hanson.xyz Dev 72b932b25e Add on-demand media URL refresh for expired MLS Grid tokens
MLS Grid media URLs expire after ~24 hours. Instead of running
scheduled syncs, this adds on-demand refresh when images are requested:

- Add is_url_expired() to parse expires timestamp from media URLs
- Add refresh_media_urls() to fetch fresh URLs from API for one listing
- Add get_property_media() API method using ListingId filter
- Image endpoint checks URL expiration before fetching
- If expired, refreshes URLs from API then proceeds with fetch

This is more efficient than scheduled full syncs because:
- Only refreshes URLs for listings actually being viewed
- Zero overhead for unviewed listings
- Scales naturally with traffic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 23:45:44 -06:00
Hanson.xyz Dev 4db53b607c Add WebP thumbnail endpoint for MLS property images
- Create MLS_Image_Endpoint class with on-demand thumbnail generation
- Use ImageMagick to convert images to WebP format
- Thumbnail sizes: 800px (thumb) and 1800px (full), maintain aspect ratio
- Only downsize images, never upsize
- Cache thumbnails in wp-content/uploads/mls-thumbnails/
- Add mls_get_image_url() helper function (1-based index)
- Update property cards to display thumbnail as background-cover image
- Long cache headers (1 year) with ETag support

URL format: /mls-image/{listing_key}/{index}/{size}/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 23:28:59 -06:00
Hanson.xyz Dev 198c9b9091 Add marker clustering and responsive full-width layout
- Integrate Leaflet.markercluster for map performance with large datasets
- Add brand-colored cluster markers (small/medium/large sizes)
- Reduce individual pin size to 17x22px
- Implement LayoutCalculator for dynamic content centering
- Full-width property archive with constrained filters/hero
- Map max 33% width, cards exactly 400px each
- JS calculates optimal column count and sets CSS custom properties

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 22:59:57 -06:00
Hanson.xyz Dev fc018ca604 Integrate MLS listings with property map and add smart sync
Property Map:
- Replace ACF-based property display with MLS database queries
- Use real lat/lng coordinates from MLS (100% coverage)
- Create property-card-mls.php template for MLS property cards
- Update AJAX handler to filter MLS properties

MLS Plugin Enhancements:
- Add 'wp mls run' smart sync command (auto-detects full/incremental/resume)
- Add database index migrations for lat/lng and composite search indexes
- Add comprehensive README.md documentation

Documentation:
- Update site README.md with sysadmin quick reference
- Add FEATURES_PENDING_12_15.md tracking client feature requests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 22:32:41 -06:00
Hanson.xyz Dev b9cddd2f64 Refactor MLS sync to Active/Pending only with on-demand media
Major changes to sync strategy following MLS Grid best practices:

- Initial sync now fetches only Active/Pending properties (~30K vs 1.3M)
- Replication (incremental) fetches all changes, deletes non-Active/Pending
- On-demand media fetching replaces background queue (avoids rate limits)
- Media downloaded and cached when first viewed, not during sync
- Updated CLI commands: wp mls media status/fetch/clear
- Comprehensive documentation with troubleshooting guide

This fixes the "Value out of range" API error caused by high $skip values.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 08:25:37 -06:00
Hanson.xyz Dev 6eadf3d266 Add queue-based media download system with rate limiting
- Add download_status, retry_after, queued_at columns to mls_media table
- Add mls_media_log table for download attempt tracking
- Rewrite media handler to queue downloads instead of immediate download
- Add 700ms delay between downloads (25% buffer over 2/sec limit)
- Add 3-hour backoff for rate-limited (429) responses
- Add max 5 attempts before marking as permanently failed
- Add wp mls media command: status, process, reset, logs
- Deprecate wp mls sync media in favor of wp mls media process
- Update documentation with queue system details and cron examples

Media downloads are now separate from property sync:
1. wp mls sync full/incremental - syncs properties, queues media
2. wp mls media process - downloads queued media with rate limiting

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 22:52:58 -06:00
Hanson.xyz Dev b62867d834 Add sync recovery commands for interrupted syncs
- Add wp mls recovery list to show resumable syncs
- Add wp mls recovery auto to auto-resume most recent failed sync
- Add wp mls recovery cleanup to mark stale syncs (>1hr) as failed
- Track last_next_link during incremental sync pagination
- Add get_resumable_syncs(), cleanup_stale_syncs(), auto_resume() methods

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 22:33:27 -06:00