== Changelog ==

= 2.13.0 =

Inventory sync, log maturity, and vendor neutrality

Inventory sync

* New: S&S Activewear inventory executor — updates WooCommerce variation stock quantities via the REST API inventory endpoint, batching styles in a single request per sync
* New: SanMar inventory executor now records runs in sync_runs — inventory freshness, vendor health, and sync success rate on the dashboard are now accurate
* New: Manual "Run now" button on the Schedule page — trigger an inventory sync for any vendor without waiting for the next scheduled run
* New: 2-hour and 4-hour inventory sync intervals added to the scheduler
* Changed: SanMar WSDL URL moved from a hardcoded PHP literal into manifest.json — the executor reads it from the database like every other endpoint
* Changed: SanMar endpoint seeder now loops all manifest endpoints instead of reading only the first one
* Files: includes/vendors/ssactivewear/executor-inventory.php (new), includes/vendors/sanmar/executor-inventory.php, includes/vendors/sanmar/manifest.json, includes/vendors/sanmar/sanmar-setup.php, includes/classes/class-catalogsync-background-processor.php, includes/ajax/ajax-schedule.php, includes/ui-scheduling.php, assets/catalogsync-schedule.js, informedio-catalog-sync.php

Logging

* New: Log deduplication — consecutive identical messages for the same vendor increment a repeat count instead of inserting duplicate rows
* New: Auto-escalation — INFO promotes to WARNING at 24 repeats, WARNING promotes to ERROR at 72 repeats
* New: repeat_count and last_seen_at columns on the logs table
* New: Repeat count badge (×N) in the Tools > Logs viewer
* Changed: Vendor display names shown in log badges, dropdown, and copy/email output — "S&S Activewear" instead of "ss"
* Changed: Staging import log entries (CS-ST-001, CS-ST-002) switched from catalogsync_vendor_log to catalogsync_log so they write in production without requiring debug mode
* Files: includes/database-setup.php, includes/helpers/debug-helpers.php, includes/ajax/ajax-tools-logs.php, includes/assets/catalogsync-tools.js

Reliability

* New: Orphaned sync run cleanup — runs stuck in "running" for 2+ hours marked "abandoned" during daily maintenance
* New: sync_runs retention pruning on the same daily schedule as log pruning
* Changed: start_sync_run / finish_sync_run promoted to public static methods with sync_type parameter — single canonical writer for all run types
* Changed: Inventory sync status derived from actual outcome (complete / partial / failed / skipped) instead of hardcoded "complete"
* Files: includes/helpers/debug-helpers.php, includes/classes/class-catalogsync-background-processor.php

Vendor neutrality

* Fixed: Core staging executor (executor-staging.php) replaced six S&S-specific function calls with vendor-neutral catalogsync_get_table_columns / catalogsync_filter_row_to_columns
* Fixed: 12 default-to-'ss' fallbacks removed — ajax-preview.php (6), ajax-staging.php (3), ajax-download.php (1), ajax-filters.php (1), staging-helpers.php (1), tmp-paths.php (1), ui-product-import.php (1)
* Fixed: is_free_vendor() — two diverged copies in ui-vendors.php and ui-scheduling.php removed; both now delegate to CatalogSync_License::is_free_vendor()
* Fixed: ensure_vendor() identity lookup matches on vendor_key only via prepared statement — removed OR vendor_name clause that could create duplicate vendor rows if a shop owner renamed a vendor
* Fixed: S&S endpoint seeder wrote to nonexistent file_format column instead of file_type — value was silently dropped by filter_row_to_columns
* Fixed: Dashboard vendor display name resolver (catalogsync_dash_vendor_display_name) reads from the vendors table with static caching instead of a hardcoded array
* Fixed: Dashboard vendor health avatar color check uses strict vendor_key equality instead of stripos substring matching
* Fixed: ajax-tools.php contained duplicate log fetch/clear functions conflicting with ajax-tools-logs.php — duplicates removed
* Files: includes/import/executor-staging.php, includes/ajax/ajax-preview.php, includes/ajax/ajax-staging.php, includes/ajax/ajax-download.php, includes/ajax/ajax-filters.php, includes/ajax/ajax-tools.php, includes/helpers/staging-helpers.php, includes/helpers/tmp-paths.php, includes/ui-product-import.php, includes/vendors/ssactivewear/ssactivewear-setup.php, includes/vendors/sanmar/sanmar-setup.php, includes/dashboard-kpis.php, includes/ui-dashboard.php

= 2.12.0 =

Extension points for add-ons

* New: `catalogsync_after_catalog_build` action — fires after a successful catalog rebuild, allowing add-ons to post-process catalog rows (e.g., AI enrichment, translation, category mapping) before the executor pushes them to WooCommerce
* New: `catalogsync_dashboard_after_mid_row` action — fires between the mid row (chart + vendor health) and the bottom row (activity log + scheduled actions) on the dashboard, allowing add-ons to inject their own cards using the existing .cs-dash-card visual language
* Documentation: both hooks are documented via inline doc-blocks describing the contract, parameters, and stability guarantees for future add-on developers
* Files: informedio-catalog-sync.php, includes/catalog-builder.php, includes/ui-dashboard.php, readme.txt, changelog.txt

= 2.11.0 =

Keyword indexing, guided imports, and clearer mapping

* New: Keyword index — comma-delimited columns (e.g. KEYWORDS) are parsed into individual terms after each import, so the Field Explorer shows real keywords like "scrub" or "medical" with accurate row counts instead of the raw cell
* New: Click-to-use in the Field Explorer now creates a filter rule automatically when none exists for that field, instead of only adding to an existing rule
* New: Replace-existing-data option on each Import card — clears that vendor's staging data before importing so re-downloading the same brand doesn't stack duplicate rows. Per-card, so importing a second brand with the option unchecked keeps the first brand's rows.
* New: Plain-language image field descriptions on the Map tab — each gallery image column now shows what it is (e.g. "Worn by model, front — changes per color" vs "Main style photo — same for all colors")
* New: Resizable preview panel on the Map tab — drag the divider to widen or shrink the live preview
* Fixed: Variation color images now attach correctly — the "Variation Color Image" role was restored to the Map tab and its save/compile path, so per-color images map to each variation again (regression from v2.6.0)
* Fixed: Field Explorer values with surrounding quotes are now stripped before building the filter clause, so exact-match filters work as expected
* Fixed: Map tab JavaScript now cache-busts on version change, so UI updates load without a manual hard refresh
* Files: keyword-index.php (new), image-field-hints.php (new), ajax-filters.php, ajax-import.php, ajax-mapping.php, compiler-mapping.php, executor-import.php, ui-tab-import.php, ui-tab-mapping.php, ui-product-import.php, catalogsync-filters.js, catalogsync-import.js, catalogsync-mapping.js, informedio-catalog-sync.php

= 2.10.0 =

Field Explorer & file structure overhaul

* New: Field Explorer panel on the Filter tab — select any field to see all distinct values with row counts, sorted by frequency or alphabetically
* New: Click-to-use — click any value in the Field Explorer to insert it into the active filter rule
* New: Search within field values for fast lookup in high-cardinality columns (e.g., COLOR_NAME, KEYWORDS)
* New: Browse Fields chip bar — explore any staging column without adding a rule first
* New: Resizable filter/explorer panels — drag handle between the rule builder and Field Explorer to adjust column widths
* New: Field explore AJAX endpoint (catalogsync_filter_field_explore) returning grouped distinct values with counts
* Fixed: Saving SOAP credentials no longer wipes SFTP password and vice versa — blank password fields on update are now treated as "unchanged"
* Changed: Plugin file structure reorganized — classes to includes/classes/, tab templates to includes/tabs/, standalone AJAX handlers consolidated into includes/ajax/
* Changed: Filter JS version string now uses CATALOGSYNC_VERSION constant for automatic cache-busting on every release
* Files touched: informedio-catalog-sync.php, ui-product-import.php, ajax-filters.php, ajax-vendor.php, catalogsync-filters.js, catalogsync-admin.css, readme.txt, changelog.txt

= 2.9.0 =

Enhanced logging

* New: Structured error code system (CS-XX-NNN) across all log entries — every message carries a unique, greppable code for fast diagnosis
* New: Human-readable log messages — store admins can understand what happened without reading source code
* New: ERROR-CODES.md internal reference mapping all 72 error codes to their component, severity, and message
* Changed: Removed table names, filesystem paths, function names, hook names, and DB error strings from all log output and AJAX error responses
* Changed: Diagnostic context (vendor, endpoint, HTTP code, byte counts, row counts) preserved in structured JSON context column
* Changed: catalogsync_log_sync_summary rewritten with CS-UP-001 through CS-UP-101 codes and human-readable labels
* Changed: SFTP transport fail/log methods no longer expose host:port in context — uses component tagging instead
* Changed: Download handler AJAX error responses no longer include internal table names or temp file paths
* Changed: flush_error_log source tags use generic component names instead of filenames
* Files touched: 20 files across ajax handlers, executors, transport, scheduler, tools, vendor setup, and debug-helpers

= 2.8.0 =

Dashboard — KPI overhaul

* New: Operational KPI dashboard replacing the pipeline wizard layout
* Five live KPI tiles: catalog coverage (WC products vs catalog SKUs), inventory freshness (hours since last sync with target threshold), sync success rate (30-day rolling window), avg sync duration (with max), in-stock rate (with out-of-stock count)
* Each KPI tile shows a trend badge (up/down/flat) and degrades gracefully to "Awaiting sync data" when sync_runs history is not yet populated
* Vendor health cards: per-vendor summary with catalog row count, last catalog sync age, last inventory sync age, and color-coded progress bars with Healthy/Stale/No data status badges
* Sync volume chart: stacked bar chart of products created vs updated per day over 14 days, rendered via inline JS from PHP-supplied JSON
* Activity log: last 7 sync_runs entries with status dot (green/red), vendor key, sync type, row counts, and relative timestamp
* Scheduled actions panel: upcoming Action Scheduler jobs filtered to catalogsync_* hooks, with countdown badges for actions within 24 hours
* Contextual alert bar: automatically surfaces stale inventory (>24h) or missing sync data per vendor
* License card preserved: activate/deactivate AJAX flow unchanged

Architecture

* New file: includes/dashboard-kpis.php — pure data layer with all SQL queries, KPI calculations, and schema-defensive helpers (table/column existence checks via information_schema)
* New file: assets/catalogsync-dashboard.css — all dashboard styles scoped to .cs-dash with CSS custom properties, responsive breakpoints at 960px and 800px
* Rewritten: ui-dashboard.php — clean template consuming data from dashboard-kpis.php, zero business logic in the view layer
* Master data assembler: catalogsync_dash_get_all_data() returns a single structured array so the template destructures once

= 2.7.0 =

Tools — Logs tab (new)

* New: DB-backed log viewer on the Tools > Logs tab — card-row layout with level badges, vendor/component pills, relative timestamps ("2 min ago") with full date on hover, and click-to-expand context JSON
* Log entries with context data show a chevron indicator; clicking expands a formatted JSON block with level-appropriate coloring (red for errors, amber for warnings)
* Filter bar: level, vendor, component dropdowns plus full-text search with 400ms debounce
* Sort: Newest first, Oldest first, Errors first — sort is applied server-side via the AJAX handler
* Per-row Copy and Email buttons appear on hover; Copy all and Email all buttons in the filter bar operate on the full visible page
* Copy formats entries as clean text (timestamp, level, vendor, component, message, indented context); Email opens the default mail client via mailto with the same formatted output
* Pagination with page count and Older/Newer navigation

Tools — Settings tab

* Settings Save button now wired — reads all logging and performance fields (debug mode, log transactions, log errors, retention days, memory limit, time limit, batch sizes) and persists via AJAX to wp_options

Tools — Database tab

* New: Per-component schema version system — each component (core tables, each vendor) has its own installed vs current version, independent of the plugin version
* Core tables version tracked via CATALOGSYNC_CORE_SCHEMA_VERSION constant in database-setup.php; vendor versions read from the version field in each vendor's manifest.json
* Installed versions stamped in wp_options after each successful migration run (catalogsync_schema_core, catalogsync_schema_{vendor_key})
* Database tab now shows a version comparison table with Installed, Current, and Status columns — status badges: Current (green), Update available (amber), Not installed (blue)
* Auto-migration on admin_init now compares per-component schema versions instead of the plugin version constant — bumping a schema file version triggers migration without requiring a plugin version bump
* "Update all" button runs only components that are behind
* Removed old catalogsync_db_version option from the activation hook — replaced by per-component stamping

Logging

* Timestamps now stored in WordPress local timezone (current_time('mysql')) instead of UTC (current_time('mysql', true))
* Log retention pruning now compares against WordPress local time instead of MySQL NOW()
* Download handler (ajax-download.php) fully instrumented — all success and failure paths now log to catalogsync_logs with vendor and component context, including curl exit codes, remote URLs, and stderr output

Vendor credentials

* Download handler credential resolution replaced — removed the six-deep null coalescing fallback chain with direct reads from the canonical user_name and api_key columns, preventing silent auth failures when earlier columns exist as empty strings
* Save button on the Vendor Setup form now starts disabled with "Test connection before saving" hint, preventing accidental credential wipes from saving with the password field auto-cleared on vendor selection

S&S Activewear

* Added missing remote_url to the inventory endpoint in manifest.json — downloads were failing with curl exit code 2 (empty URL)

= 2.5.0 =

Vendor management (new)

* New: Vendors submenu — vendor management is now a standalone admin page separate from the Add Products pipeline, giving vendors the space they need as the registry grows
* Landing page shows all vendors as cards in a two-column grid, split into Installed and Available sections
* Each installed vendor card shows avatar, account number, connection type (API/FTP), tier badge (Free/Pro), catalog row count, last verified timestamp, and SOAP credential status for FTP vendors
* Available but not-yet-installed vendors appear as dimmed cards with an Install button wired to the existing vendor discovery and install system
* Configure button on each card navigates to a per-vendor detail page with stats, credential forms, and connection log — replacing the old show/hide config card pattern with a real server-side page load
* Detail page pre-populates all form fields from the database — no more JS-driven field population via handleVendorSelection()
* SOAP credentials section on the detail page renders server-side for FTP vendors only, replacing the CSS/JS visibility toggle
* Execution log available on both the landing page (for install feedback) and the detail page (for connection test output)

Admin UI

* Removed the Vendors tab from the Add Products tab bar — the tab bar now starts at Download, which is the natural first step after vendor setup
* Default tab on Add Products changed from Vendors to Download
* "Manage vendors" button in the Add Products top bar now links to the new Vendors submenu instead of the old Vendors tab
* New cs-vendor-* CSS block appended to catalogsync-admin.css — card grid, badges, stat metrics, detail header, responsive breakpoints, following the same design language as the Schedule page

Files changed: admin-menus.php, ui-product-import.php, catalogsync-admin.css
Files added: ui-vendors.php

= 2.4.0 =

Inventory sync (new)

* New: SanMar inventory executor at includes/vendors/sanmar/executor-inventory.php — lightweight stock-quantity updater using the PromoStandards Inventory V2.0.0 SOAP API
* Queries WooCommerce for parent products tagged _catalogsync_vendor_key = 'sanmar', collects distinct style numbers, makes one SOAP call per style to GetInventoryLevels
* Per-warehouse quantities are summed per SKU (partId = UNIQUE_KEY = WC variation SKU), then _stock and _stock_status are updated on the matching variation
* Only touches products already in WooCommerce — no staging tables, no file downloads, no product creation
* SoapClient is reused across all style calls within a single run to avoid reconnecting per call
* WSDL caching enabled (WSDL_CACHE_BOTH) to reduce overhead on repeated runs
* Summary logged on completion: styles processed, variations updated, stock changes, newly out-of-stock, SKUs not found, errors (capped at 25)
* Pricing updates deferred to a future release — the SanMar getPricing response does not include uniqueKey, making SKU matching non-trivial; stock is the high-frequency use case

Scheduler integration

* Scheduled inventory sync jobs now dispatch to vendor-specific executors via a keyed action hook (catalogsync_run_inventory_executor_{vendor_key}), keeping the shared scheduler vendor-neutral — no vendor-specific logic in ajax-schedule.php
* SanMar executor registers on catalogsync_run_inventory_executor_sanmar; other vendors add their own hooks without touching shared code
* Replaced the previous no-op stub that logged "no lightweight executor yet, skipping"

Vendor credentials

* Added SOAP API credential fields to the Vendor Setup form: Customer Number, Web Services Username, Web Services Password — shown only for FTP-type vendors
* Three new columns on catalogsync_vendors: soap_customer_number varchar(50), soap_username varchar(255), soap_password varchar(255)
* SOAP credentials (sanmar.com login) are separate from SFTP credentials per SanMar's FTP Integration Guide
* Vendor selection JS populates SOAP fields from saved data; save handler persists them to the database
* SOAP section uses its own id="soap-credentials-section" toggled by JS, avoiding the CSS visibility pitfall of the group-ftp class which uses display:none !important

Product sync

* executor-products.php now stores _catalogsync_vendor_key postmeta on parent products during catalog sync — the inventory executor uses this to query "all SanMar products" without vendor-specific logic in the query
* Existing products get tagged on their next Update run; no retroactive migration required

Admin UI

* Download and Import tabs now render endpoint cards in a two-column tile grid instead of full-width stacked cards, cutting vertical space roughly in half
* Removed progress bars and log boxes from Download and Import tiles — replaced with a single inline status label per tile that shows Ready, Downloading/Importing, OK, or error text
* Removed legacy process-to-staging handler from import JS — all endpoint buttons now use the same dynamic handler

= 2.3.0 =

Scheduling (new)

* New: Schedule submenu — automated catalog and inventory sync intervals, configured per vendor, kept separate from the manual Add Products workflow so automation and hands-on operation don't compete for the same screen
* Catalog and inventory now run on independent schedules — catalog (styles, images, descriptions, pricing) defaults to weekly; inventory (stock and price only) is configured separately, since inventory data changes far more often than catalog data
* Schedule intervals map to real Action Scheduler recurring actions; changing an interval, or setting it back to manual, always clears any previously scheduled action first so interval changes never leave a stale duplicate running the old cadence
* Scheduling remains gated to Pro Developer (Price ID 2) and Agency (Price ID 3) tiers, consistent with existing Action Scheduler licensing; unlicensed installs see an upgrade prompt in place of the schedule controls

Automated sync pipeline

* Scheduled catalog sync now runs the full pipeline, not just the final update step — Download and Import are triggered automatically before Filter/Map/Update, so a scheduled run pulls fresh vendor data instead of re-syncing whatever was staged from the last manual run
* FTP vendors: the file manifest is refreshed, previously selected files are re-downloaded, and downloaded files are imported into staging automatically; if the download step fails outright, the run aborts rather than rebuilding from stale staging data
* API vendors: every enabled endpoint is downloaded and imported in the order declared by the vendor's endpoint configuration; a single endpoint failing is logged and skipped so the other endpoints still update rather than blocking the entire run
* Download logic for both FTP and API vendors was extracted into reusable functions so the same code runs from either a manual button click or an automated schedule — no duplicated download logic between the two paths

Sync history

* Every background sync run now records its vendor, status, timing, and processed/changed/failed row counts for future reporting
* Note: inventory-only sync fires correctly on its configured interval, but the lightweight stock/price-only executor it depends on has not shipped in this release — scheduling it today registers the job safely with no effect until that executor ships in a future update

Admin UI

* Renamed the "Import Console" submenu to "Add Products" for clarity
* New Schedule submenu added alongside Dashboard and Add Products

= 2.2.1 =

Migrations

* Auto-migration on version change — the plugin now compares its stored schema version against the running code version on every admin page load and runs the full migration automatically when they differ, using the same shared migration routine as the Tools tab's Run All button so the two paths can never drift apart

Download tab

* New: Downloaded Files panel — a disk-scan based list of files sitting in the plugin's tmp directory, showing file name, date, and size, with a summary line above the list showing total count, total size, and date range

Update tab

* Unfiltered-build guard — builds with no active filter and no explicit confirmation are now blocked outright; a checkbox showing the live staged-row count appears whenever no filter is active, preventing an accidental full-catalog import
* Auto-purge of superseded FTP downloads — before downloading a newer dated file for a given brand or category, older local copies of that same brand/category are automatically deleted, preventing the tmp directory from accumulating stale files across sync runs

= 2.2.0 =

Framework

* Catalog builder rewritten — reads join declarations from vendor_endpoints instead of hardcoded table joins; supports both multi-table and single-table vendor layouts through the same code path
* vendor_endpoints schema extended with join_to, join_on, and join_type columns; join relationships are declared in vendor manifests and seeded at install time
* Filter and mapping example values now work for any staging layout — single-table vendors no longer require a styles table to populate field hints
* Import tab supports per-file cards for FTP-transport vendors alongside existing API endpoint cards
* Vendor installer functions resolved via discovery — no hardcoded vendor function names in shared code

Product sync (executor)

* Executor reads normalized catalog columns directly — removed hardcoded S&S staging-name translation table; all vendors now sync through the same deterministic code path
* Variable product grouping uses a canonical style-key resolver that reads whichever style identifier column is present in the catalog (styleID for S&S, style_no for SanMar), so single-table flat vendors build variable products with color/size variants on the same path as multi-table vendors
* Self-healing variation upsert — when a SKU is held by a wrong-shape product (e.g. a stale simple product from an earlier run), the stale post is deleted to free the SKU and the variation is rebuilt correctly; a re-run repairs products whose shape changed instead of failing on duplicate-SKU collisions

Images

* Image Manager recognizes absolute_url source mode (used by SanMar) as equivalent to full_url — previously, absolute URLs silently returned empty and images never attached
* Color Image and Brand Image added as mapping targets — per-color variation images and brand logos now populate from the mapping profile for all vendors
* Mapping compiler carries _color_image_url and _brand_image targets through to the catalog builder so the image columns are filled during catalog assembly

Mapping

* Added color_image_url (Color Image) and brand_image (Brand Image) to the Map tab target list
* Updated mapping notes: image_url, color_image_url, and brand_image reflect their active role in the Image Manager rather than future-ready placeholders

Tools

* Database Migration rebuilt as discovery-driven per-vendor Repair system — each discovered vendor gets its own Repair button with inline status feedback; Run All processes core tables and all vendors sequentially
* Vendor selector auto-submits on change — Set Vendor button click no longer required

UI

* Fixed elapsed timer regression — startElapsed() now clears any existing interval before creating a new one, preventing orphaned timers that kept incrementing after job completion

Licensing

* Added EDD Software Licensing integration — activate, deactivate, and validate licenses from the Dashboard card
* S&S Activewear is the free-core vendor and works without a license or restrictions
* Additional vendors (SanMar first) require a valid Pro license; selecting a premium vendor without a license redirects to the Dashboard with an upgrade prompt
* All vendor-scoped AJAX operations are gated — premium vendor sync requests return an error without a valid license; S&S operations pass through freely
* License status cached in a 7-day transient; weekly WP-Cron re-check confirms subscription health without hitting the server on every page load
* Action Scheduler automated syncing gated to Pro Developer (Price ID 2) and Agency (Price ID 3) tiers
* Dashboard license card shows activation status, tier label, masked key, and expiration date; unlicensed users see an "Upgrade to Pro" upsell with link to informedio.com

= 2.1.4 =

Maintenance

* Removed a stray debug log write (file_put_contents to fatal.log) that bypassed the plugin's protected logging mechanism

= 2.1.3 =

Review compliance and hardening

* Updated bundled DataTables from 1.13.11 to 2.3.8 (latest stable)
* Ajax endpoint is no longer hardcoded — now passed to JS via wp_localize_script() using admin_url()
* SQL identifier handling tightened: preview field validated against the table's actual columns; vendor key validated against the vendors registry before building any table name
* Removed dead Open Preview button and its orphaned server-side handler (depended on a missing file)
* Removed placeholder Schedule tab from the Import Console

= 2.1.2 =

Compliance and hardening

* Renamed plugin to "Informedio Catalog Sync for WooCommerce" for a distinct, brand-led name
* Runtime data (cached vendor payloads, logs) is now stored under the uploads directory instead of the plugin folder
* Added recursive input sanitization to the mapping and update-product AJAX handlers
* Updated bundled DataTables to 1.13.11
* Moved the admin menu to a lower position to respect the core WordPress menu hierarchy

= 2.1.1 =

Product structure

* Products now group by styleID only — all color variants of a style are consolidated into one variable product instead of creating a separate product per color
* Color (pa_color) is now a variation attribute alongside size (pa_size), enabling color + size selectors on the single product page
* Each variation carries both attribute_pa_color and attribute_pa_size postmeta after save

Image pipeline

* New: variation color images — colorFrontImage attached to each product variation via WC_Product_Variation::set_image_id() so the correct photo appears on color selection
* New: brand logo images — brandImage attached to WooCommerce product_brand taxonomy terms via thumbnail_id so logos appear on brand archive pages and sidebar filters
* Variation image cache scoped per style group — same color URL sideloaded once regardless of how many sizes share it
* Brand image cache static across all style groups in the request — each brand logo downloaded once per sync run
* Image failures wrapped in try/catch and logged as warnings; product saves never interrupted by image errors

Brand assignment

* Added wp_set_object_terms() fallback for brand assignment when WooCommerce Brands feature toggle is off or set_brand_ids() is unavailable
* Pending brand stored in product meta and flushed immediately after parent save to handle new products where post_id is not yet known at assignment time

Manual edit protection

* Categories on existing products are only updated when the product currently has no categories assigned, preserving any manually set categories
* New products always receive categories from the feed

Schema

* Added brandImage varchar(255) to staging_styles table
* Added brand_image varchar(255) to staging_catalog table
* catalog-builder SELECT now includes COALESCE(s.brandImage, '') AS brand_image

Tools

* New: Run Database Migration button in Catalog Sync → Tools — fires dbDelta() against all Catalog Sync tables to safely add new columns without touching existing data

= 2.1.0 =

Filter builder

* Per-rule AND/OR logic: each rule chooses how it joins to the rule above it, replacing the single global connective
* Nested groups: rules can be collected into groups with their own logic, enabling compound conditions such as BrandA OR BrandB OR (BrandC AND title contains "scrub")
* Compiler rewritten as a recursive tree walk; every group emits its own parenthesized clause to preserve explicit precedence
* Empty groups are skipped during compilation rather than emitted as (1=1), preventing accidental match-everything branches when an OR-joined group has no children
* Backward compatible: filter profiles saved under the previous flat shape continue to load, render, and compile unchanged
* Saved-filters list now counts rules across the full tree, not just the top level

Synchronization performance and logging

* Deferred WC_Product_Variable::sync() to the end of each batch instead of firing per variation group, eliminating a severe per-row performance regression on large brands
* Added a static guard around catalogsync_ensure_wc_attribute() to memoize taxonomy-existence checks within a batch
* Per-batch progress is now logged with rows-per-second telemetry and surfaced in the update UI progress bar (real percentage rather than a hardcoded mid-run value)
* Unified background-processor and executor logging through a single structured logger with consistent log levels and correlation context

Schema

* total_rows is now captured on the import job row and exposed in the background status response so the UI can compute true progress

= 2.0.0 =

* Major architecture redesign to endpoint-based ingestion
* Removed monolithic catalog ingestion model
* Introduced multi-endpoint staging
* Added inventory warehouse normalization
* Implemented assembled catalog working dataset
* Reworked ETL pipeline
* Introduced stream-to-disk downloads
* Complete UI and workflow overhaul of the ETL console
* Improved scalability for large vendor catalogs
* Refactored execution to use vendor_key for deterministic file resolution
* Standardized execution via single entry point with endpoint routing
* Introduced specialized handling for nested inventory data
* Improved reliability for large inventory imports
* Enhanced execution consistency for nested vendor payloads
* Resolved issues where inventory imports could complete without inserting data
* Eliminated duplicate execution conflicts
* Enforced strict separation of execution responsibilities

= 1.0.1 =

* Fixed JS loading issue causing handleVendorSelection undefined error
* Removed API URL dependency from vendor configuration
* Replaced API URL with Vendor Key
* Hid SFTP host and storage path from UI
* Improved vendor selection logic to be fully database-driven
* Removed hard-coded vendor assumptions
* Introduced deterministic vendor installation architecture
* Added vendor manifest and installer pattern
* Implemented vendor discovery system
* Fixed installer path resolution issues
* Stabilized connection test and save flow
* Fixed download corruption issues
* Resolved JSON parsing errors

= 1.0.0 =

* Initial public release