# AuctionForge Changelog

All notable changes to the AuctionForge plugin are documented in this file.

---

## [3.6.2.2] — 2026-04-29

### Catalog-sync kind disambiguation

#### Fix
- `catalog_sync` jobs target a single auction that may be either active or archived. Until now their alerts were always classified as `kind=upcoming`. The classifier now resolves the auction's `auction_state` term meta and routes:
  - `ARCHIVED` / `ENDED` / `POST_AUCTION` → `kind=past` → **Kill + Full Past** buttons
  - anything else (or unresolvable) → `kind=upcoming` → **Kill + Full Upcoming** buttons

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/class-auto-updater.php`

---

## [3.6.2.1] — 2026-04-29

### Telegram Sync Monitoring follow-ups

#### Features
- **Context-aware alert buttons.** Each Telegram alert now shows the action set most relevant to the failing sync: upcoming-sync errors get **Kill + Full Upcoming**, past-sync errors get **Kill + Full Past**, everything else gets **Kill** only. Button choice is driven by a new `kind` field classified at log-write time from the queue's `job_type`.
- **Plugin version in alerts + Update button.** Each push now includes the live `plugin_version`. wpdeploy compares it to the latest `is_stable=1` row in its `versions` table and only renders an **Update plugin** button when the site is behind. Tapping Update calls the existing `POST /wp-json/auctionforge/v1/trigger-install` endpoint to force the upgrade.

#### Architecture
- New private helper `AuctionForge_Auto_Updater::classify_error_kind($jobId)` looks up the job's `job_type` and maps it to `'upcoming'` / `'past'` / `'other'`. Used by both the immediate push and the 1-min retry cron.
- Outbound payloads now include `plugin_version` at the top level; wpdeploy refreshes `sites.plugin_version` on every `/sync-errors` POST.

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/class-auto-updater.php`

---

## [3.6.2.0] — 2026-04-29

### Telegram Sync Monitoring & Remote Control

#### Features
- **Instant Telegram alerts on sync errors.** Any `level='error'` row written to `af_sync_log` is pushed (fire-and-forget, non-blocking) to wpdeploy, which fans it out to a configured Telegram group with inline-keyboard action buttons (Kill / Full Upcoming / Full Past / Acknowledge).
- **Remote command surface.** Any group admin can tap a button to:
  - **Kill sync** — flips DB flags via `SyncQueue::cancelAll()`, then SIGTERMs every active worker; a 5-second watchdog escalates to SIGKILL for any survivor.
  - **Full sync upcoming** — full re-import of every active auction and its lots (queued, observable, killable). Replaces the `re-sync?do=importBidSpiritUpcoming` flow with a queued equivalent.
  - **Full sync past** — same, for archived auctions.
- **Safety-net retry cron** (`auctionforge_retry_sync_errors`, every minute) re-pushes recent error rows. wpdeploy dedupes via `UNIQUE(site_id, af_log_id)` so duplicates are silent.

#### Architecture
- **`SyncLogger::log()`** now fires `do_action('auctionforge_sync_error_logged', …)` on `level='error'` so any handler can react. The auto-updater listener does a 2 s non-blocking POST to `/api.php/sync-errors`.
- **New REST endpoint** `POST /wp-json/auctionforge/v1/sync/cancel-all` (X-API-Key auth) — runs `SyncQueue::cancelAll()` + SIGTERM + watchdog. Logs every action through `SyncLogger`.
- **`POST /sync/start` extended** with a `mode` parameter (`upcoming` default, `past` for archived). Bulk re-sync is now a queued job, not a synchronous block.
- **New job types** `full_sync_upcoming` and `full_sync_past` on `SyncQueue`, dispatched to a new `BulkSyncWorker` that wraps the existing `AuctionList::init()` + `LotList::init($upcoming)` pipeline.
- **`syncWorker.php`** installs a SIGTERM handler that flips the in-flight job to `failed` with a "Killed by admin (SIGTERM, cleanup OK)" marker and closes the DB cleanly before exiting. Watchdog escalates to SIGKILL after 5 s grace.
- **`LotList::auctionProcessing()`** now heartbeats the active bulk-sync job between auctions, preventing the safety cron from auto-failing long bulk runs as stale (the 600 s threshold is unchanged).

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/sync/SyncLogger.php`, `inc/sync/SyncQueue.php`, `inc/class-auto-updater.php`, `crontasks/syncWorker.php`, `inc/import/LotList.php`
- **Created:** `inc/sync/BulkSyncWorker.php`

---

## [3.6.1.6] — 2026-04-29

### High-Value Lot Modes for `[bp_lot_list]`

#### Features
- **`high_value_past` mode** — Displays past auction lots ordered by their catalog estimate (`estimatedPriceInt`), highest first. Useful for "Top Lots" or "Highlights" sections.
- **`high_value_sold` mode** — Displays past sold lots ordered by their actual sold price (`soldBid`), highest first. Only includes genuinely sold lots (`soldBid > 0`) and reuses the same false-positive guard as `random_past` to skip lots where `soldBid` was carried over from `startPrice` without real bidding activity.
- **Admin documentation updated** — The Shortcodes reference page in WP Admin now lists both new modes in the `mode` attribute description and includes example shortcode usage.

#### Architecture
- New constants `MODE_HIGH_VALUE_PAST` and `MODE_HIGH_VALUE_SOLD` on `LotsWidget`.
- New method `LotsWidget::getLotListHighValue($mode, $limit)` builds the query, applies the past-auction tax filter, sets `bp_orderby` to the relevant meta field with `DESC` order, and (for the sold variant) filters out unsold-but-stale lots in PHP.
- New filter hook `bp_lot_list_high_value_args_filter` for downstream customization of the WP_Query args.

#### Files Changed
- **Modified:** `inc/widgets/LotsWidget.php`, `inc/controllers/ShortcodesPage.php`

---

## [3.6.0.18] — 2026-04-25

### Brevo SMS Notifications Integration

#### Features
- **Transactional SMS via Brevo API** — The plugin now sends text message alerts through the Brevo transactional SMS service, running alongside existing Firebase push notifications.
- **Four Notification Types:**
  - **New Auction Published** — Broadcast SMS to all opted-in subscribers when an auction is published.
  - **Auction Starting Soon** — Targeted SMS to users registered for an auction when it is about to start.
  - **Favorited Item Live Soon** — Targeted SMS to users who favorited a lot when it is approaching in the auction.
  - **Outbid Notice** — SMS sent when a user's leading bid is surpassed, with a 5-minute per-item cooldown to prevent spam.
- **SMS Subscriber Database** — New `af_sms_subscribers` table stores user phone numbers, country codes, and per-notification-type opt-in preferences (GDPR-ready explicit consent).
- **Admin Settings Tab** — New "SMS Notifications" tab in Settings with:
  - Brevo API Key input
  - SMS Sender Name (11 char max)
  - Master enable/disable toggle
  - Per-notification-type on/off switches
  - Test SMS widget with phone input and send button
  - Live subscriber count display
- **Profile SMS Preferences** — Users can opt into SMS notifications directly from their profile page with phone number entry and granular notification type selection.

#### Architecture
- **BrevoSMS Service** (`inc/services/BrevoSMS.php`) — cURL-based API wrapper with broadcast, targeted, and single-send methods, message builders with 160-char limit awareness, rate limiting, and comprehensive error logging.
- **SMSSubscriber Model** (`inc/model/SMSSubscriber.php`) — CRUD operations, email-to-phone resolution, opt-in filtering, and upsert logic.
- **Webhook Integration** — SMS dispatch hooks added to `AuctionsAPI.php` methods: `leading_bid_surpassed()`, `item_live_soon()`, `auction_starts_soon()`, and `auctionPublishedPush()`.

#### AJAX Handlers
- `auctionforge_sms_save` — Save SMS preferences from profile page
- `auctionforge_sms_load` — Load SMS preferences for profile page
- `auctionforge_sms_test` — Admin-only test SMS endpoint

#### Database
- New table: `af_sms_subscribers` — Phone numbers, country codes, per-notification opt-ins, timestamps

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/coreIncludes.php`, `inc/controllers/AuctionsAPI.php`, `inc/controllers/Settings.php`, `admin/view/settings.php`, `templates/parts/profile-templates.php`
- **Created:** `inc/services/BrevoSMS.php`, `inc/model/SMSSubscriber.php`

---

## [3.6.0.7] — 2026-04-14

### Hotfix

#### Fixes
- **Namespace resolution** — Fixed fatal error in `wp_footer` commission label hook caused by unqualified `SettingsHelper` and `Settings` class references. Now uses fully qualified namespaces.

#### Files Changed
- **Modified:** `auctionforge.php`

---

## [3.6.0.6] — 2026-04-14

### Commission Label Override Fix

#### Fixes
- **Theme template override compatibility** — Moved the commission label JS override from the `single-lot.php` template into `auctionforge.php` via `wp_footer` hook (priority 99). Sites with theme-level template overrides (e.g. `themes/*/auctionforge/single-lot.php`) were not picking up the inline script. The `wp_footer` approach works universally on all single lot pages.

#### Files Changed
- **Modified:** `auctionforge.php`, `templates/single-lot.php`

---

## [3.6.0.5] — 2026-04-14

### Commission Label Override

#### Features
- **Commission Label Setting** — New dropdown in Settings → Appearance allowing admins to choose between "Buyer's Premium" (default) and "Auction House Commission" as the label displayed on lot pages. This overrides the Bidspirit JS client's default label using a MutationObserver that catches asynchronously injected text.

#### Files Changed
- **Modified:** `inc/controllers/Settings.php`, `templates/single-lot.php`

---

## [3.6.0.4] — 2026-04-12

### Auction Approval Modal Redesign

#### Changes
- **Removed BidSpirit Terms of Use** — The generic BidSpirit "Terms of Use" section that appeared at the top of the non-credit-card approval modal has been removed. The modal now shows only the auction house's own legal agreements managed within the plugin.
- **Renamed Modal Title** — Changed "Terms of conditions" to "Terms & Conditions".
- **Larger Scroll Area** — The Terms & Conditions scroll box is now 350px tall (up from 200px) for easier reading.
- **Proportional Section Heights** — Refund & Return Policy and Chargeback Waiver sections use a shorter 260px scroll box to visually distinguish them from the primary Terms & Conditions.

#### Files Changed
- **Modified:** `templates/js-chunks/approval-info.php`, `assets/js/app.js`

---

## [3.6.0.2] — 2026-04-10

### Webhook Token Generator

#### Features
- **Token Generator Widget** — Added a 16-character random token generator directly below the "Webhook auth token" field on the Settings page. Includes a "Generate Token" button and a clipboard copy icon with visual feedback.

#### Files Changed
- **Modified:** `inc/controllers/Settings.php`

---

## [3.6.0.1] — 2026-04-10

### Patch

#### Changes
- Version bump.

---

## [3.6.0.0] — 2026-04-10

### Legal Agreements for Auction Approval

#### Features
- **Terms & Conditions** — A mandatory, scrollable Terms & Conditions document is now displayed during the auction approval process. Bidders must scroll to the bottom and check an agreement box before their approval request is submitted. Always enabled.
- **Refund & Return Policy** — An optional, scrollable Refund & Return Policy can be enabled in Settings. When enabled, bidders must read and accept it during auction approval.
- **Chargeback Waiver** — An optional, scrollable Chargeback Waiver can be enabled in Settings. When enabled, bidders must read and accept it during auction approval.
- **Admin Management** — New "Legal Agreements" page under the Bidspirit admin menu with tabbed TinyMCE editors for each agreement type. Includes automatic version numbering (incremented on content change) and full version history.
- **Consent Logging** — Every agreement acceptance is logged to a dedicated database table (`af_user_agreement_log`) with the agreement type, version, user email, IP address, user agent, auction ID, and timestamp.
- **User Tracking Integration** — Agreement events (`terms_agreement`, `refund_agreement`, `chargeback_waiver`) are also recorded in the existing IP tracking system alongside registration, login, and bid events.
- **Placeholder Content** — Default sample content is seeded on plugin activation for all three agreement types.

#### Settings Added
- **Enable Refund & Return Policy** — Toggle to require bidders to accept the refund policy during auction approval.
- **Enable Chargeback Waiver** — Toggle to require bidders to accept the chargeback waiver during auction approval.

#### Database
- New table: `af_legal_agreements` — Current active agreement content per type
- New table: `af_legal_agreement_versions` — Version history archive
- New table: `af_user_agreement_log` — User consent records

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/controllers/Settings.php`, `inc/controllers/BidspiritViews.php`, `inc/ip-logger.php`, `templates/js-chunks/approval-info.php`, `assets/js/app.js`
- **Created:** `inc/legal/class-legal-agreements.php`, `admin/view/legal-agreements.php`

---

## [3.5.0.6] — 2026-04-08

### Bid Event Tracking

#### Features
- **Bid Tracking** — All successful bids (regular, catalog, pre-sale buyout, post-sale purchase) now fire `afLogIP("bid", bidspiritUser)` to capture the bidder's full identity in the user tracking table.
- **Whitelist Update** — Added `bid` to the allowed `event_type` values in both the local plugin and the central WPDeploy API.

---

## [3.5.0.5] — 2026-04-08

### Self-Healing Database Schema

#### Features
- **Auto-Migration** — The plugin now auto-detects and adds any missing tracking table columns on version upgrade, ensuring sites with older schemas seamlessly migrate without manual SQL.

---

## [3.5.0.4] — 2026-04-08

### API Key Auto-Healing

#### Features
- **Seamless Self-Registration** — The WordPress plugin now autonomously regenerates and saves a fresh API Key whenever WPDeploy rejects a payload with an `api_key_collision` error. This guarantees zero-downtime security handovers when developers clone instances to proxy/staging servers.

---

## [3.5.0.3] — 2026-04-08

### API Key Collision Defense

#### Features
- **Network-Level Quarantine** — Central API dynamically extracts and strictly validates the incoming payload domain against the registered API Key domain, forcefully blocking mismatched traffic (HTTP 403).
- **Local Critical Alert Banner** — Added a persistent crimson warning banner in the WordPress admin panel that intercepts `api_key_collision` errors, directly instructing developers to update cloned keys.

---

## [3.5.0.2] — 2026-04-08

### Modal Trigger Hotfix

#### Fixes
- Restructured internal UI rendering priority to gracefully deploy the inline Modal triggers inside custom IP log view without colliding with cached `z-index` stacks.

---

## [3.5.0.1] — 2026-04-08

### Hotfix

#### Fixes
- **Timed Auction Badge Visibility** — Fixed a strict type-matching bug in the main `auction_list.php` core file that incorrectly hid the "Timed Auction" badge overlay from valid timed/online auctions on the main upcoming/archive views. The badge now displays properly across all manual and Bidspirit-synced objects when toggled on.

#### Files Changed
- **Modified:** `templates/main/auction_list.php`

---

## [3.2.9.1] — 2026-03-30

### Timed Auction Display Enhancements

#### Features
- **Timed Auction Countdown Text Toggle** — Added a WP-Admin setting "Change text to 'Auction will close in'" that conditionally changes the countdown label for timed/online auctions on single lot pages.
- **Shortcode Text Toggle Support** — Extended the new countdown text logic to the `bp_upcoming_wide` shortcodes so "Live bidding starts in:" successfully transitions to "Auction will close in:" for timed auctions.

#### Files Changed
- **Modified:** `inc/controllers/Settings.php`, `templates/single-lot.php`, `templates/shortcodes/upcoming-auction-wide.php`, `templates/shortcodes/upcoming-auction-wide-manual.php`

---

## [3.2.9.0] — 2026-03-29

### Billing & Invoices Polish

#### Fixes
- **Profile Invoice ID Tracking** — Prevented internal auction codes (e.g. `002` or `ai4`) from breaking into the clean hyphenated titles inside the `profile-templates.php` view.
- **Dynamic Payment Statuses** — Replaced Bidspirit's default/cached `NOT PAID` HTML payloads by wiring the `dompdf` generator to directly respect the current `$b.status` string from the API (resolving instances where a paid invoice still printed "NOT PAID" atop the document).
- **Payment Instructions Conditional** — Automatically hide the "Payment Instructions" block from the PDF footer when an invoice is fully paid.
- **Improved Nomenclature** — Changed "Invoice No:" to "Bidder No:" on document output to prevent confusion with static invoice ledger IDs.

---

## [3.2.6.0] — 2026-03-28

### Multi-Day Auction Lot Filtering Fix

#### Fixes
- **Day 2+ showing lots from Day 1 in multi-day auctions** — The `itemIndex` column in `bp_meta_data` is `varchar(55)`, but the day-range filter in `BidspiritQuery::bp_query_where()` was comparing it as a string. With string comparison, lot numbers like `'11'`, `'101'`–`'145'` incorrectly fell between `'1001'` and `'1450'`, causing Day 1 lots to leak into Day 2 results. Fixed by using `CAST(bpMeta.itemIndex AS UNSIGNED)` for numeric comparison and `intval()` on the PHP-side boundary values. Letter lots (e.g. `14A`, `1025A`) are handled correctly — `CAST` strips the suffix, keeping them grouped with their base lot number.

#### Files Changed
- **Modified:** `inc/controllers/BidspiritQuery.php`

---

## [3.2.5.9] — 2026-03-27

### Catalog Page Fix

#### Fixes
- **Duplicate preview hours on catalog pages** — The `displayHours` field was rendered twice: once via the `bp_after_description_auction` hook in `BidspiritViews.php` and once inline in the auction template. Removed the hook-based rendering to eliminate the duplication.

#### Files Changed
- **Modified:** `inc/controllers/BidspiritViews.php`, `templates/main/auction.php`

---

## [3.2.5] — 2026-03-19

### Sync Reliability & Stuck Lot Prevention

#### Fixes
- **CATALOG_PUBLISHED webhook now triggers catalog sync** — Previously only sent push notifications without syncing lots. When the earlier AUCTION_CATALOG_UPDATED fired before the catalog was ready on BidSpirit, lots were never imported. CATALOG_PUBLISHED now calls `auctionCatalogUpdated()` to trigger a full catalog sync when the catalog is actually available.
- **0-lot API results now fail instead of silently succeeding** — When BidSpirit API returns 0 lots (catalog not yet ready), the sync job now fails with an explicit error message, allowing the next webhook to create a fresh retry job.
- **Expanded CDN allowlist in pre_http_request filter** — The HTTP filter during bulk post insertion now allows all `*.bidspirit.com` subdomains and BidSpirit image CDNs (`fastly.net`, `cloudfront.net`). Previously only `bidspirit.com` and `www.bidspirit.com` were allowed, blocking image downloads during sync and causing worker hangs.
- **InsertLotsBS timeout guard** — Stage 5 (InsertLotsBS processing loop) now has a 180-second timeout and logs progress every 2 batches. Prevents indefinite hangs that previously required manual force-kill.
- **fixResult() no longer marks lots as done when postId=0** — When `lotProcessing()` fails to create a WP post, the staging row now keeps `syncFinished=0` so the lot is retried on the next sync pass, instead of being permanently marked as complete with no actual post.
- **lotProcessing() logs error on missing auction term** — Previously returned 0 silently when `Auction::findAuctionById()` failed. Now logs an explicit error that feeds into `fixResult()` to keep the lot retryable.
- **Hourly orphan detection in safety cron** — New step in `syncSafety.php` detects lot posts that exist in `bp_meta_data` but are missing their auction taxonomy assignment (caused by workers killed mid-batch) and auto-repairs them with `wp_set_object_terms()`.

#### Files Changed
- **Modified:** `inc/controllers/AuctionsAPI.php`, `inc/sync/CatalogSyncWorker.php`, `inc/abs/InsertLotsAbstract.php`, `inc/import/InsertLotsBS.php`, `crontasks/syncSafety.php`

---

## [3.2.4] — 2026-03-18

### Sync Worker Hang Fix

#### Fixes
- **Sync workers hanging at InsertLotsBS stage** — Third-party plugins (Smush Pro, SmartCrawl SEO, WPMU DEV Updates) hook into `wp_insert_post()` and make blocking outbound HTTPS calls for every lot processed. With 489+ lots per auction, this caused workers to hang indefinitely on socket reads to WPMU DEV servers (`32.193.94.25`, `32.193.93.106`). Added `WP_IMPORTING` constant at the REST worker entry point and a `pre_http_request` filter in `CatalogSyncWorker` that blocks all non-essential external HTTP during the batch post-insertion phase. Loopback (`127.0.0.1`, `localhost`) and BidSpirit API calls remain allowed.
- **Stuck staging rows after force-kill** — When workers were force-killed mid-sync, staging rows in `import_history` were left with `syncstart=1` and never unblocked. Subsequent sync runs could not see these rows (filtered out by `Lot::getRawLots()`), causing the InsertLotsBS loop to exit immediately with 0 posts processed. Combined with job deduplication (`SyncQueue::enqueue()` skips if a `processing` job exists), this created a deadlock where no new sync could proceed.

#### Files Changed
- **Modified:** `auctionforge.php`, `inc/sync/CatalogSyncWorker.php`

---

## [3.2.2] — 2026-03-17

### Full Sync Auction Discovery Fix

#### Fixes
- **Full Sync finds no auctions on fresh sites** — The v3 `full_sync` action only queried the WordPress database for existing auction terms, but on a freshly deployed site the database has zero auctions. Added `AuctionList::init()` discovery step before querying the DB, so Full Sync now fetches all auctions from the BidSpirit API and creates taxonomy terms first, then enqueues catalog sync jobs for each.

#### Files Changed
- **Modified:** `auctionforge.php`

---

## [3.2.0] — 2026-03-14

### Webhook Pipeline Fixes, Archive Sync & Admin UI Overhaul

#### New Features
- **Archive Sync** — New "Archive Sync" button on Sync Status admin page. Enqueues catalog sync jobs for all ARCHIVED/ENDED auctions. Past auctions now appear in the auction dropdown with an optgroup separator and state label.
- **Sync Tasks v3** — Replaced old v2 task cards (importData, importBidSpirit, lotBidSpiritOld) with v3 queue-based tasks. "Sync Active Auctions" and "Sync Archived Auctions" now enqueue jobs via the queue system. Maintenance tasks (Clean Draft/Trash Lots, Clean Orphaned Meta) remain as direct-execution tasks.
- **How It Works guide rewritten** — Complete rewrite of the admin guide for v3 architecture. Documents webhook-first flow, all 5 job types, queue system mechanics, cron setup (external + WP-Cron), configuration constants, and troubleshooting.

#### Fixes
- **Dual-write removed** — Removed legacy handlers from `auctionCatalogUpdated()`, `auctionDayEnded()`, and `itemsUpdated()` webhook methods. The old handlers were racing with the v3 worker, creating orphaned staging rows in `import_history` that were never processed. All three methods now exclusively use the v3 enqueue → spawn path.
- **ITEMS_UPDATED webhook** — Fixed `itemsUpdated()` failing to enqueue jobs. The `ITEMS_UPDATED` payload has `auctionId` nested in `itemsByLanguage.{lang}[0].item.auctionId`, not at the top level. Added extraction logic to resolve it.
- **ItemUpdateWorker incomplete** — `ItemUpdateWorker::process()` was only calling `Lot::bulkSave()` (staging table) without running `InsertLotsBS` to process staged data into WP posts and `bp_meta_data`. Added the InsertLotsBS processing step with autocommit restore. Item updates now flow: stage → InsertLotsBS → WP post + meta → cache clear.
- **Worker spawn reliability** — Changed `SyncQueue::spawnWorker()` from non-blocking HTTPS to blocking HTTP loopback (`http://127.0.0.1` with explicit `Host` header, 2s timeout). Fixes nginx 403/404 errors when HTTPS loopback hits the default vhost instead of the site's vhost.
- **API token constant** — Plugin expects `AUCTIONFORGE_API_TOKEN` in `wp-config.php` (renamed from `UCO_API_TOKEN` during the plugin rename). Documented in readme.

#### Database
- DB version: 22 → 23

#### Files Changed
- **Modified:** `inc/controllers/AuctionsAPI.php`, `inc/sync/ItemUpdateWorker.php`, `inc/sync/SyncQueue.php`, `auctionforge.php`, `admin/view/sync-status.php`, `readme.txt`

---

## [3.0.0] — 2025-03-14

### New Feature: Webhook-First Sync Architecture (Sync v3)

**Overview:** Complete redesign of the BidSpirit data synchronisation pipeline. Replaces the old two-phase polling system (importBidSpirit every hour + importData every minute) with a webhook-driven job queue that processes data in near real-time.

#### Architecture
- **Webhook-driven sync** — BidSpirit webhooks now enqueue jobs into a dedicated queue table (`af_sync_queue`) instead of relying solely on polling cron tasks. Data flows: webhook → queue → background worker → WordPress posts.
- **Dual-write mode** — During transition, webhooks enqueue jobs AND still run the legacy handlers. Once verified, legacy handlers can be removed cleanly.
- **Job deduplication** — Prevents duplicate work by checking for existing pending/processing jobs with the same auction_id + job_type.

#### New Components
- **Job Queue System** (`inc/sync/SyncQueue.php`) — Priority-based FIFO queue with atomic job claiming, status tracking (pending/processing/completed/failed/skipped), worker PID tracking, and automatic stuck job recovery.
- **Sync Logger** (`inc/sync/SyncLogger.php`) — Real-time log table for admin console polling with level filtering (info/success/warning/error) and incremental cursor-based retrieval.
- **Catalog Sync Worker** (`inc/sync/CatalogSyncWorker.php`) — Processes catalog_sync, full_sync, and day_ended jobs by reusing existing `LotList::singleRun()` logic.
- **Item Update Worker** (`inc/sync/ItemUpdateWorker.php`) — Processes individual item_update jobs with the same logic as the webhook `itemsUpdated` handler.
- **Background Worker** (`crontasks/syncWorker.php`) — Async CLI process spawned by webhooks via `exec()` or by the safety cron. Claims and processes queued jobs with a configurable timeout (default 240s).
- **Safety Net** (`crontasks/syncSafety.php`) — Hourly cron that resets stuck jobs (>10min), spawns workers for pending jobs, runs a 24-hour health check (auto-enqueues full sync if no webhooks received), and purges old data.

#### Admin UI
- **Queue Status Dashboard** — New card on Sync Status page showing pending/processing/completed/failed/skipped counts, recent jobs table with real-time refresh, worker status badge, and action buttons:
  - **Full Sync** — Enqueues catalog sync for all active auctions
  - **Sync Selected** — Enqueue a single auction from dropdown
  - **Spawn Worker** — Manually trigger a background worker process
- **Live Console** — Dark-themed terminal console on the Sync Status page. Polls `af_sync_log` every 1.5s showing timestamped, color-coded log entries from webhooks, workers, queue operations, and the safety cron. Supports pause, clear, and auto-scroll.

#### Cron Changes
- **Removed** `importData` (ran every minute — ~1,440 PHP executions/day)
- **Removed** `importBidSpirit` (ran hourly — ~24 PHP executions/day)
- **Added** `syncSafety` (runs hourly at :10 — 24 PHP executions/day)
- **Net effect:** ~62 PHP executions/hour → 1 PHP execution/hour
- **WP-Cron safety hook** (`auctionforge_sync_safety`) — Always-active hourly event, independent of external cron
- **setup.sh v3** — Updated task definitions, new `migrate` command for interactive v2→v3 transition

#### Database
- New table: `af_sync_queue` — Job queue with status, priority, source, attempts, worker PID, timing columns
- New table: `af_sync_log` — Real-time log entries with context, level, auction_id, job_id
- DB version: 20 → 22

#### Constants Added
| Constant | Default | Description |
|---|---|---|
| `AUCTIONFORGE_SYNC_WORKER_TIMEOUT` | 240 | Worker process timeout in seconds |
| `AUCTIONFORGE_SYNC_STALE_JOB` | 600 | Seconds before a processing job is considered stuck |
| `AUCTIONFORGE_SYNC_HEALTH_CHECK` | 86400 | Seconds without webhook before triggering full sync |
| `AUCTIONFORGE_SYNC_LOG_RETENTION` | 7 | Days to keep log entries |
| `AUCTIONFORGE_SYNC_QUEUE_RETENTION` | 30 | Days to keep completed queue jobs |
| `AUCTIONFORGE_CURL_TIMEOUT` | 30 | BidSpirit API cURL timeout in seconds |
| `AUCTIONFORGE_PHP_BINARY` | /www/server/php/83/bin/php | PHP CLI binary path for spawning workers |

#### AJAX Handlers Added
- `auctionforge_sync_log_poll` — Incremental log polling for live console
- `auctionforge_sync_queue_status` — Queue counts + recent jobs for dashboard
- `auctionforge_sync_enqueue` — Admin-triggered sync actions (full_sync, sync_auction, spawn_worker)

### Fixes
- **cURL timeout** — BidSpiritRequest now enforces a 30-second timeout (was unlimited/0), preventing hung connections from blocking the sync pipeline.

### Files Changed
- **Modified:** `auctionforge.php`, `inc/coreIncludes.php`, `inc/controllers/AuctionsAPI.php`, `inc/BidSpiritRequest.php`, `admin/view/sync-status.php`, `crontasks/setup.sh`
- **Created:** `inc/sync/SyncQueue.php`, `inc/sync/SyncLogger.php`, `inc/sync/CatalogSyncWorker.php`, `inc/sync/ItemUpdateWorker.php`, `crontasks/syncWorker.php`, `crontasks/syncSafety.php`

---

## [2.7.3.0]

### Fixes
- Various stability improvements and bug fixes.

## [2.7.1.2]

### Fixes
- Added missing `vatAndCommission()` function in `loadBid()` — the sales tax display on single lot pages was never being rendered because the code to call `loadTemplate` for `vat-and-commission-template` was missing from `app.js`.

## [2.7.1.1]

### Fixes
- 2 decimal places now only appear in the bid modal (Your max bid, Total Price) via `showDecimals` flag. Start price, increments list, and other prices show whole numbers as before.

## [2.7.1.0]

### Fixes
- Fixed Buyer's Premium value not showing on single lot pages.

## [2.7.0.9]

### Fixes
- Fixed fatal error during plugin install/upgrade: `class-stats-database.php` require failure.

## [2.7.0.0]

### New Features: Advanced Lot Analytics
- Conversion Tracking, Peak Viewing Times, Pre-Sale vs Live Comparison, Category Performance, Share Analytics, Search → View Funnel, Click-Through Tracking, CSV Export, Hot Lot Badge.
- New DB table: `af_lot_shares`. New columns on `af_lot_analytics`. DB version bumped to 20.

## [2.6.0.0]

### New Feature: Lot Analytics
- Comprehensive lot view and favorite tracking system with admin dashboard.

## [2.5.0.0]

### New Features
- **Site Statistics System** — Full website analytics built into the plugin admin.
- **Recently Viewed Items** — Profile page tab showing last 20 viewed lots per user.
