Changelog

All The Parts catalog platform, complete history

Product Pictures From the Catalogue's Own Store, Sized for the Device v1.16.3

imp Every product picture, document and sound file is served from the catalogue's own store instead of Parts Europe's, as Parts Europe's terms require; new articles' pictures are copied within minutes of arriving.
imp Each picture also comes in three smaller sizes, and the shop loads the one each place needs: a listing card on a phone takes about a fifth of the full picture, the enlarged view the full one.
new A discreet "© Parts Europe" notice on the product picture, in the quick view and in the enlarged view, and Parts Europe named as the picture's copyright holder in the product's structured data.

The Menu Works in Every Copy on a Page v1.16.2

fix A page that carries [partseurope_mega_menu] twice (a header dropdown and a phone side panel) had a working menu only in the first copy: in the second, a tap on a group opened its page at once, or did nothing for the groups without a page. One listener now serves every copy, including one added later.

The Menu Takes the Width It Is Given v1.16.1

fix [partseurope_mega_menu] takes exactly the width of the place it is put in. In a header dropdown that sizes its content to fit, it had none: the groups stood one word per line and the tiles did not show.
fix The menu keeps its own look under any theme: a theme's style for buttons (capitals, letter spacing, height) no longer reaches the group names.

The Category Menu as Parts Europe's v1.16.0

new [partseurope_mega_menu]: Parts Europe's six top categories in its order, each opening its second level as tiles with Parts Europe's pictures (our own copies), then the search tools (tire search, chain kits), the promotions and the brands and catalogues. A category that has a store on the site opens in the store. Built on the server and refreshed hourly; on a touch screen the first tap shows a group and the second opens it.
imp Three top categories carry the shop's own names.

Sold Where Parts Europe Sells API

fix An article is on sale when any of Parts Europe's sources lists it, its own category pages included, not only the daily price file: articles Parts Europe sells on its site were missing from the shop (the luggage kits branch was empty). A discontinued article stays off. The hourly check refuses to write when the listings shrink suddenly.
imp A brand that appears this way gets its brand page without waiting for the next day.

Tires, Vehicles and Filters, Checked Against Parts Europe API

fix Where Parts Europe's own tire fields contradict the size printed in the article name, the name's size and its ZR mark are shown, marked as such; radial against bias stays Parts Europe's. The product page no longer shows the file-only "_EU" rows.
fix Parts Europe's two groups with the same name (Type on tires, Mounting Position on brakes) are two groups, as on its site.
fix A value with commas inside brackets, or one Parts Europe lists whole ("12 per Case, Sold Each"), is no longer split.
fix Fitments are joined by part number and Parts Europe's vehicle id: articles whose vehicle list had lost vehicles show them again.
fix The vehicle box says "loading" while a list loads, not "no matches"; a late answer no longer replaces a newer choice; the pencil works after a reload; the recent vehicles offered are those the listing has parts for.
imp Chain kits: when no chain of the stock length exists, the same chain in the lengths there are ("shortened to fit"); drive values nothing can be found by are left out.

Parts Europe's Printed Catalogues v1.15.0

new /catalog/catalogs: every catalogue Parts Europe lists, filtered by keywords, market and brand in the address; a catalogue reads page by page with the articles printed on the page beside it, and the page can be downloaded. The product page opens its catalogue at that page. Product sections only, from our own copies without Parts Europe's name.

Vehicle Search, Tire Search, Chain Kits v1.14.0

new Parts Europe's Vehicle Search from the vehicle card: brand, engine size, model or sales name and year in any order, or HSN and TSN, with keywords, a table of vehicles and the recently selected ones.
new /catalog/tire-search: by size, by the size printed on the tire, or the tires that fit your bike - over the tires listing, every field a type-to-filter list.
new /catalog/chain-kits: the vehicle first, its stock final drive, then the kit, the same chain with other gearing, the chains and the sprockets that fit.
fix Tire fields as Parts Europe spells them, read from the size printed in each tire's name; model codes no longer read as widths.
fix The vehicle choices beside a listing offer only vehicles with parts in it, each with the number the listing will show; years newest first; one brand per name; one BMW HSN.
fix A vehicle chosen inside a category with nothing for it lands on its parts; fitments Parts Europe withdrew are not counted.
imp Several attribute filters at once answer in milliseconds.

Parts Europe's Brands and Promotions v1.13.0

new /catalog/brands/ lists the house brands and every brand with its logo; /catalog/brands/<brand> is that brand's listing, with its categories and filters. An article links its brand's page; the brand name is Parts Europe's.
new /catalog/product-stream/<promotion>: Parts Europe's two clearance promotions with their banner and category tiles; each tile lists what the promotion holds in that category.

The Catalogue at Parts Europe's Addresses v1.12.0

new The catalogue lives at real addresses on the shop's own site, shaped as Parts Europe's: /catalog/category/<path>, /catalog/search?s=..., /catalog/product/<name>/<article>, the filters in the query. Moving between them does not reload the page; each can be shared, bookmarked and read by a search engine.
new Every address carries its own title, description and canonical, through Yoast SEO; an article also carries its schema.org Product. An address that names no article or category answers 404.
new The search suggestions offer Parts Europe's categories, with the branch they sit in ("Chains - Final Transmission").
new Stores stay, as branches of Parts Europe's tree: [partseurope cat="tires-and-tubes" title="..."] makes any page a store, with the same addresses under its own (/gumi/category/...); its menu is its branch, its breadcrumb starts at the store. An article opens at its one address under /catalog/. A new store is a new page.
imp Cart rows, carousels, the search box and the vehicle finder all lead to the new addresses.
new A way back: the platform serves every plugin release by version and offers the one it is told to, so a deploy no longer moves the shop to a release; the widget follows the plugin that loads it; and the plugin (from 1.10.2) can snapshot the pages it changes, write a snapshot back and install an exact release.
imp Removed: the old per-store shortcodes, the pe_* query parameters and the #/ links.

Parts Europe's Filter Column, Then Ours API

new The filter column is Parts Europe's, in Parts Europe's order: brands, vehicle brand, categories, segments, availability, special sale and clearance, mounting position, then the category's own filters. "Find parts for your bike" stays on top; the shop's own filters follow Parts Europe's under "More filters": the price range, tire sizes, chain pitch.
new Vehicle Brand: the makes of the vehicles a part fits, as Parts Europe names them.
new Segments: ATV, Harley, Helmet & Apparel, Offroad, Snow, Street, Vintage Harley, Watercraft, Scooter, Indian, Bicycle.
new Special Sale and Clearance, as Parts Europe splits them: its reductions, its clearance lists, or either.
new In Stock means stock in either warehouse, as on Parts Europe; the shop keeps its own choice of warehouse beside it.
new Parts Europe's sorts (part number, price, newest, oldest, vendor part number, brand), 12 to 48 per page, and its two views as it has them: By Parts, every article, first and by default, and By Products. The counts beside the filters count what the view lists.
imp The "with photo", "with fitments", "for whom" and apparel size filters are gone: Parts Europe's own gender and size filters cover the last two.

The Filters Parts Europe Offers API

new Each category offers the filters Parts Europe offers for it, in the same order - full-face helmets: colour, finish, features, gender, graphic, model, shell material, size and more - with the number of products each choice leaves.
new Mounting Position, as Parts Europe offers it (Front, Rear, Front Left...), where on the vehicle each fitment puts the part.
imp The counts beside the filters are computed per dimension from its own values and answer in a few milliseconds.

What Parts Europe Shows Beside an Article API

new The product page carries the specification grid Parts Europe publishes, homologation, OEM numbers, the article's notes, related parts, alternatives, replacements and similar products.
new Colour first, then the sizes that colour comes in, each with its stock. Every article of a product is reachable; the size buttons stopped at sixteen.
new The printed-catalogue pages an article is on, shown from our own sharp copies.
new Manufacturer and EU responsible person (GPSR), at the bottom of the product page.

Prices: Only What the Shopper Pays v1.10.1

imp Product pages tell search engines the price the shopper pays, VAT included.
new A reduction is Parts Europe's own: when PE has reduced an article, the card, the quick view and the product page show the price before the sale, struck through, and the cut. The "discounted" filter and the discount carousel show exactly these articles.
imp Price sort, the price range and the price histogram use the price the shopper pays.
fix Search results page past the first 24. A search showed its first page and no way to the rest.

Sync: Network Errors Retried

fix A connection that fails at the network level (DNS, refused or reset connection, TLS) is now retried with backoff, like a server error. About 1% of sync cycles used to lose their first step to one, and the error now names the real cause instead of a bare "fetch failed".
imp The API no longer announces its framework in an X-Powered-By header.

All The Parts Everywhere v1.10.0

imp Every plugin notice and the update feed name the plugin All The Parts Catalog.
imp The one-off sync-plugin removal action from 1.9.1 is gone: it has done its job.
imp The platform no longer serves the retired sync plugin, the project-scope page or the old design preview.

Retired Sync Plugin Removed Safely v1.9.1

fix A one-off admin action removes the retired sync plugin: it deactivates it, deletes only its own settings and product markers by exact name, and deletes its folder without running its uninstall routine, which would have deleted unrelated product data.

New Home: catalog.alltheparts.eu v1.9.0

new The catalog platform now lives at catalog.alltheparts.eu. The widget, the catalog API and the plugin's own updates load from there.
new The platform address is a plugin setting (the pe_platform_host option, or PE_HOST in wp-config.php), so a future move needs no plugin release.
new The plugin is now called All The Parts Catalog.

Bulgarian Rejection Message + Fixed-cart Warning v1.8.3

fix On a Bulgarian storefront the coupon rejection now shows in Bulgarian. WooCommerce's Store API returns coupon errors in English on this site (a site-wide WC translation gap — even "coupon does not exist" comes back English), so the message our scoped coupons trigger is localized via WC's own coupon-error filter. The broad fix for all WC messages is to install the WooCommerce Bulgarian translation (Dashboard → Updates → Update translations).
new Live admin warning — selecting "Fixed cart discount" while a PartsEurope scope is set now shows an inline warning that the scope will be ignored (fixed-cart is whole-cart by WooCommerce design). Use Percentage or Fixed product discount for a scoped discount.

Coupon Rejection Managed by WooCommerce v1.8.2

fix Removed the custom "no matching product" message. WooCommerce already rejects a Percentage / Fixed-product coupon when no cart line is in scope, using its own storefront-localized message — the custom one only duplicated it and leaked the admin's language onto the storefront. WC now owns that message.
imp Panel note clarified: the scope applies to "Percentage discount" and "Fixed product discount" only. "Fixed cart discount" ignores the scope and discounts the whole order — it is a whole-cart type by WooCommerce design; use Percentage or Fixed product for a scoped discount.

Coupon Panel Follows Admin Language v1.8.1

fix The "PartsEurope scope" coupon panel was authored with Bulgarian source strings, so an English-language WordPress admin still saw Bulgarian labels in Marketing → Coupons. The panel now follows the WP admin language — English for an English admin, Bulgarian for a Bulgarian admin. The customer-facing "no matching product" message follows the storefront locale too.

Scoped Promo Codes (by category / brand / SKU) v1.8.0

new WooCommerce coupons can now be restricted to a PartsEurope category, sub-category, brand or specific SKUs — even though the whole catalog rides on a single placeholder product. The coupon edit screen (Marketing → Coupons → "Usage restriction") gains a "PartsEurope scope" panel with dropdowns fed from the live catalog.
new Empty = no restriction (whole store). Several fields = intersection ("Category: Helmets" + "Brand: Alpinestars" = Alpinestars helmets only). The discount applies only to the matching cart lines — resolved server-side by SKU, never trusting a client-sent price or scope.
imp Works for "Percentage discount" and "Fixed product discount" (WooCommerce evaluates these per line); "Fixed cart discount" applies to the whole order. Both the classic cart and the Cart/Checkout Blocks (Store API). All native fields (usage limits, expiry, min/max spend, free shipping) apply on top.
fix A coupon with no matching product in the cart is rejected with a clear message instead of applying a zero discount. No WooCommerce taxonomy or product import needed.

Live Chip Counts + Combo Sizes + Protection Gear Sizes v1.7.25

new Every hero chip (sub-categories, sizes, materials, MIPS, chain pitch/ring, brands) now re-counts on each filter change through the bitmap facet engine — picking size L instantly shows how many products each brand/sub-category has in L. Chips that would yield 0 results grey out and disable; selected chips never lock; a size row that is all zeros hides entirely.
fix Protection Gear (27), Head & Neckwear (25), Eyewear (26) and Watercraft Apparel (32) showed size chips that returned 0 products — the apparel parser never covered those categories. "Protection Gear + L" went from 0 to 130 products.
new Combo sizes parse correctly: "S/M", "LG/XL", dash "M-L", glued "LXL" (and helmet pads "M/2X") write BOTH letter sizes, so either size filter finds the product. Deliberate whitelist for glued forms — "ML" is millilitres on oil bottles, "SM" is plain Small.

Category Renames + Hero Reorder + EU Shoe Sizes v1.7.24

imp Client-requested sub-category corrections (Helmets, Footwear & Socks, Protection Gear): five labels did not match the actual products inside (PE's own data) — e.g. the "Shoe accessories" sub-category held MX boots, "Number plates" held protection vests. Renamed in BG + EN + DE at once; pinned as source='manual' so the Gemini taxonomy translator never overwrites them.
imp Sub-categories now follow a curated display_order (client-specified for Helmets / Footwear / Protection; rest by product count) — both in the hero tiles and the sidebar tree.
imp Hero section order unified: sub-categories on top → sizes (ascending) → material/protection (helmets) → brands at the bottom.
new A "Brands" row replaces the old "Top brands" row on all dedicated pages: every brand present in the category with exact bitmap-facet counts (the old row guessed top-8 from the first 100 products), collapsible at 15 chips.
new Footwear sizes in EU numbering — chips show "43 (US 9.5)". New apparel_shoe_size_eu attribute via unified US→EU tables (men / women / youth; youth detected by name token or the Youth Boots sub-category). Glued sizes ("BLACK10.5") and dropped-dot halves ("105" → 10.5) now parse; footwear coverage 89.3% → 92.9%.
new Recategorization infrastructure: parts_taxonomy_overrides table re-applies manual SKU-level category corrections after every sync batch, so PE's data can't silently revert them. Currently empty by client decision (PE's category structure stays untouched); available whenever a reshuffle is wanted.
fix Apparel, helmet and chain attribute parsers now run on a 10-min tick like the tire parser — new parts from sync used to never get size/material attributes (filters silently froze to the last manual backfill).
fix Footwear size chips on the product page: the apparel-size siblings strategy only recognised letter sizes (S/M/L), so shoes and boots with numeric sizes never showed their size row. Com 34 now groups by the base name minus a trailing numeric token, and chips read EU-first with the original US size as a hint ("47 (US 13)"), using the parser's audience-aware conversion. Listing cards show the numeric size badge too.

Homepage Search Widget Fix v1.7.23

fix Standalone search widget on Elementor-built homepages — submit ("Search") + suggestion clicks were no-ops because [partseurope_search] auto-detect could not find the catalog page: WordPress's has_shortcode() only inspects post_content, but Elementor stores all widgets/shortcodes in the _elementor_data meta JSON. Helper pe_get_catalog_page_url() now does a second pass over Elementor meta when the classic scan returns nothing.
imp Both [partseurope_search] and [partseurope_carousel] now reuse pe_get_catalog_page_url() (was duplicated buggy auto-detect logic). Custom elements <pe-search> + <pe-carousel> also accept a data-catalog-url attribute and fall back to PEConfig.mainCatalogUrl, so SKU clicks always land on the right page.

Vehicle Finder Label v1.7.22

imp Default label changed from "Find parts for your bike" to "Search by bike" to match the menu item; 🏍 icon dropped.

Apparel Card Title + Size Siblings v1.7.19–v1.7.21

fix Listing card titles now use the translated title.bg / title.en instead of raw part_name, so card and product detail show the same name (was: a raw code like "POLO AKRAPOVIC WMN BK L" on the card vs the translated title on detail).
new Apparel size siblings — /products/:sku/siblings recognises a new apparel-size strategy (com_codes 25–34, 50, 85). Strips the trailing size token (S/M/L/XL/2XL/...) from part_name and groups SKUs that share the base. Detail page renders the size chips with the existing siblings UI.
new Apparel size badge — small chip in the top-right corner of the listing card image showing the size token, so SKUs that share the same translated name are visually distinguishable in a grid.

Cart Action Row Polish v1.7.15–v1.7.18

fix Three buttons after add-to-cart ("To cart / Checkout / Add more") now share equal flex-basis and a fixed height (48px detail / 42px QuickView). Stacked vertically full-width on ≤768px.
fix QuickView popup gained a horizontal scrollbar after the equalisation — grid columns used 1fr (min-content floor); switched to minmax(0, 1fr) + min-width:0 on flex parents.
fix The "✓ Added!" message in column flex layout was claiming 100% of parent height (flex-basis main-axis = column height) and pushing the buttons off-screen. Mobile media block now overrides message to flex:0 0 auto.

Scope = Page Identity v1.7.14

imp Dedicated shortcode scope (tires=03, chains=12, apparel=25..50, etc.) is now treated as page identity, not a user filter. Active-filter counter, "Clear filters" button, and active-filter chips all skip the scope category — visitors no longer see "(1) × Clear filters" on a clean store landing.
imp Sidebar checkbox for the scope category is locked (disabled + "Shop" badge); toggle is a no-op so users can't accidentally uncheck the page identity. Single legitimate exit = the breadcrumb's "← All products".
imp "Clear filters" preserves the scope (clears brand/sub/vehicle/search/etc but keeps category=<scope>).

Vehicle Finder + Breadcrumb Redesign v1.7.9–v1.7.13

new [partseurope_vehicle_finder] shortcode — horizontal Producer / Model / Year cascade + Search button. Submit redirects to the catalog with ?pe_vehicle=<ids>. Three columns desktop, stacked vertical on ≤768px. Optional default_category attr scopes the result.
new Standalone vehicle finder persists the chosen producer/model/year to My Garage (localStorage) before redirect, so the catalog sidebar resolves the pill label and pre-fills the cascade combos on Edit.
new Breadcrumb redesign — reflects WHERE the user is, not the taxonomy path. Dedicated shortcode pages: Shop Tyres › Road. General catalog: Shop › Tyres › Road. Removed the misleading "All X" root + L1 group middle level.
new "← All products" exit link rendered next to breadcrumb on dedicated shortcode pages, navigating to the auto-discovered main catalog page (PEConfig.mainCatalogUrl).
imp Filter sidebar — 2nd-level category labels now bold to match the L1 group header. Subcategories (3rd level) keep regular weight for clearer hierarchy.
imp Dedicated shortcode pages no longer auto-expand the category tree to the pre-applied leaf — visitors land with the tree collapsed and discover other categories naturally.
fix "Try without × Bike / × Search" empty-state chip clicks were no-ops because vehicle/search were missing from URL_PARAMS_FOR; filter now actually clears.

Cart UX Polish v1.7.7–v1.7.8

fix Remove-from-cart notice now shows the real PE part name — previously leaked [PE WIDGET] Cart Placeholder — DO NOT DELETE because WC reads name before session re-hydration (woocommerce_cart_item_removed_title filter)
new Cart rows deep-link back to catalog product detail — auto-discovers the [partseurope] page and appends ?pe_product=<sku>; widget parses that and navigates to #/product/<sku> on mount (woocommerce_cart_item_permalink + pe_get_catalog_page_url() helper)
fix Cart item name is now wrapped in <a> — placeholder's catalog_visibility=hidden was making WC's default template skip the anchor, so v1.7.7's permalink filter fired but never materialised as a link
new save_post_page hook invalidates the cached catalog page URL when any page is edited (re-discovers on next cart render)
imp Translator switched to ORDER BY id DESC to match catalog listing sort — newest parts translate first, so users viewing the home page see BG tabs fill in while the 6.8% tail shrinks
fix CI deploy workflow ran docker compose exec api fetch(<public hostname>) — VM has no hairpin NAT, so every post-deploy test returned "fetch failed" for 104 consecutive runs. Switched to http://localhost:3100 --api-prefix /catalog

Cart Architecture Overhaul v1.7.6

new Placeholder + cart_item_data pattern — zero per-SKU WooCommerce products for cart-only PE parts (canonical WC pattern used by Bookings, Product Add-Ons, YITH Gift Cards)
new Widget sends extensions.pe: {sku, name, price, image_url} to Store API /cart/add-item — one HTTP round-trip per click (was two: ensure-product + add)
new Server-side live price + weight lookup from catalog-api on every woocommerce_before_calculate_totals (10-min transient cache, 3s timeout) — anti-tampering
new Shipping weight hook (woocommerce_cart_contents_weight) — per-item weight from catalog-api dim_weight so Econt/Speedy price correctly
new Placeholder self-heal — recreated if admin trashes or force-deletes, idempotent on every page load
new 3-layer delete block: map_meta_cap revoke + pre_(trash|delete)_post filters + WC REST woocommerce_rest_delete_product_object short-circuit
new Placeholder renamed to [PE WIDGET] Cart Placeholder — DO NOT DELETE with warning banner on edit screen
new POST /wp-json/pe/v1/purge-execute + GET /purge-scan — bulk cleanup REST endpoints with safety phrase (DELETE ALL PE PRODUCTS) + order/attachment impact preview
fix Add-to-cart broken on v1.5.5 — pe_ensure_product() created private products which WC Store API refuses (v1.5.6)
fix Cart showed placeholder image — plugin now sideloads PE image as featured via media_sideload_image() (v1.5.7, later superseded by placeholder pattern)
fix WC auto-assigned Accessories category to every cart-only product — force-cleared via wp_set_object_terms([], 'product_cat') after save (v1.6.1)
fix BG translator switched to gemini-2.5-flash-lite — 2.5-flash returning 503 UNAVAILABLE + 429 RESOURCE_EXHAUSTED under load; quality on-par, ~3× cheaper

Category-Specific Views (S1 Tires · S2 Apparel · S3 Chains · S4 Helmets)

new Dedicated heroes per category — TireHero, ApparelHero, ChainHero, HelmetHero — each with sub-category tiles + top brands row (Parts Europe .eu parity)
new Tire size-finder: cascading width × aspect ratio × diameter selector on /gumi/ landing
new Chain spec finder: pitch (520/525/530) + links count + ring-type (O/X/Z) + join-type filters
new Helmet finder: size + material (Carbon/Fiberglass) + MIPS + certification
new Apparel finder: size (letter/waist/EU chest) + audience (men/women/kids) chips
new Category aliases — Cyrillic category terms now match (e.g. chemicals + lubricants, com 36 + 37)

Dedicated WordPress Shortcodes v1.5.5

new [partseurope_tires] — dedicated tires page with TireHero + size finder
new [partseurope_chains] — dedicated chains page with ChainHero
new [partseurope_helmets] — dedicated helmets page with HelmetHero
new [partseurope_apparel] / [partseurope_gloves] / [partseurope_boots] — apparel variants with audience filter
new [partseurope_parts] — generic parts landing for all other categories
new [partseurope_carousel] — embeddable product carousels for homepages
new [partseurope_search] — standalone search widget for headers/sidebars
imp Plugin recognizes all 10 shortcodes inside Elementor _elementor_data JSON (asset loader auto-detects)

Search & Discovery

new Autocomplete dropdown — brands, categories, SKUs as you type (D1)
new Did-you-mean suggestions via pg_trgm similarity on brands + categories (D4)
new Bulgarian brand aliases — Cyrillic brand spellings now find their Latin brand (Dunlop, Michelin, ...)
new Standalone search widget with autocomplete — embed anywhere via [partseurope_search]
new Progressive skeleton loading — perceived speed upgrade while search streams in (D8)
new Smart empty-state — shows brand list + category tree when results are 0 (D9)
fix Full-text search switched to search_tsv (was ILIKE on EN part_name only — missed BG)
fix Search uses GIN tsvector natural order — fixes seq-scan when matches cluster at low IDs
fix Autocomplete category click regression — debounce closure captured stale params

Variant Grouping & Product Siblings

new /products/:sku/siblings endpoint — vendor_stem, tire-model, chain-model strategies
new Tire size chips on product detail — pick other sizes from same model (e.g. 180/55ZR17 → 190/50ZR17)
new Chain length chips — switch between 110/120/130-link of same chain model
new Canonical size formula overlay on tire product cards (e.g. "180/55ZR17 73W")
new Sibling grouping via first-part of vendor code before last dash (PE.eu logic)
fix Siblings work for single-dash size codes (e.g. DUNLOP-180-55-17)
fix Current-size chip gets stock badge + gray parens; stable 60px width (no layout shift)
fix Variant chip label drops the misleading count suffix — a "Size (7)" label looked like size 7
fix Stock badge absolute-positioned — doesn't hide size text, doesn't shift chips
fix Chip → fixed 60px width, uniform across all chips regardless of badge state

Product Detail Polish

new Price & availability sparklines — inline SVG with Δ indicator (B2 + B3)
new Share button with Web Share API fallback to clipboard copy (B11)
new Recently-viewed carousel — localStorage ring buffer (12 entries max) (B5)
new Lightbox with prev/next keyboard nav (B6)
new Fitments export — CSV download of compatible vehicles (B7)
new Currency toggle EUR ↔ BGN — localStorage-persisted preference (E8)
new QuickView specs grid + alerts chips (B8 + B9)
new Breadcrumb on product detail — category → sub-category → product
new Back-to-listing preserves filter state via sessionStorage
fix Breadcrumb links build catalog URL (no dead hashes)
fix ProductDetail shows category NAME not code
fix L1 breadcrumb filters by group level (not flat)

Filters UX

new Tire filters: width, aspect ratio, diameter cascade with live counts
new Apparel filters: size chips + audience (Men/Women/Kids)
new Chain filters: pitch + ring-type chips
new Helmet filters: size + material + MIPS
new Active filter chips above grid with one-click remove
new Auto-expand category tree when selection changes
fix Clear-filters strips pe_* query params from URL
fix Chip hover no longer fills background — avoids stale "selected" appearance after nav
fix Tire chip ordering — pass strategy to chipLabel in sort comparator
fix Sibling dedup — current chip in correct slot

Parsers & Attributes

new parts_attributes table — category-specific structured fields (size, pitch, material)
new Tire parser — metric + MT-profile + old-imperial + ATV + MTB (95.3% recall on 4,323 tires)
new Apparel parser — letter/waist/shoe-size/dual/EU-chest (90.1% recall on 9,428 apparel items)
new Chain parser — pitch/links/ring-type/join/brand (1,043 chains parsed)
new Helmet parser — size/material/MIPS/certification (1,892 helmets parsed)
new Dynamic sync hooks — parsers + brand transliteration auto-run on new products
fix Chain pitch regex — drop word boundaries (missed glued tokens like DID520VX3)
fix Tire parser — VR/TR/VB/HR/WR + MH profile + 3-digit diameter

Performance

imp Search 43s → 35ms — single-predicate tsvector WHERE + JS unaccent + brand alias pre-expansion
imp /suggest 4s → <100ms — prefix match + brands.part_count cache column + varchar_pattern_ops btree on SKU
imp /filters 60s LRU cache (language-aware key)
imp Precomputed parts.primary_image_url column — removed hot-path correlated subquery
imp Skip COUNT query when search is active (streaming mode, show N results)
imp Parallelize count + data queries (Promise.all)
imp X-Pe-Timing response header exposes count+data ms for debugging
imp Nightly search_tsv rebuild — keeps FTS fresh on brand alias edits
imp Images use decoding="async" for faster perceived load (D5)

Translations (Gemini 2.5 Flash)

new Bulk BG translator — parts.title + descriptions via Gemini 2.5 Flash (~5h full run)
new Embedded translator scheduler in API — 200 parts per 2min batch, idempotent restart
new Per-part commits — survives container restart, resumes where it stopped
new Brand name_bg + Cyrillic search aliases (Bulgarian brand spellings)
fix JSONB merge via SQL — preserve prior language translations

Storefront Homepage

new Homepage tiles with REAL product images (not emoji) linking to dedicated pages
new Standalone search widget on homepage — redirects to /catalog/?pe_search=
new 6 homepage carousel shortcodes — popular, bestsellers, trending, discount, new, category-specific
new Dedicated landing pages: /gumi/ /verigi/ /kaski/ /ekipirovka/ /rukavici/ /botushi/

Carousels

new 9 carousel sources: popular / bestsellers / trending / discount / new / category / brand / manual / fitment
new "View all" button on each carousel — combines ALL filter dimensions
new POPULARITY_CTE — popular/trending/bestsellers sorted from events table
fix Carousel layout — arrows always visible with flex layout
fix Carousel "view all" works even when plugin doesn't pass catalog_url
fix Popular/trending/bestsellers use events.sku (not event_data->>)
fix Carousel deeplinks work from homepage (subdir WP install)

Sitemap & SEO

new /sitemap.xml + /sitemap-index.xml — chunked 10k URLs per file (G5)
new Open Graph + JSON-LD tags for ?pe_product URLs (G3 + G4)
new Whitelisted sitemap paths in nginx PUBLIC_LOCATIONS allowlist

Sort & Curation

new Curated default sort — products with images first, then newest
fix Curated sort references only primary_image_url (no eu_availability → 500 fix)
fix Sort CASE expression prefix guard (no "p." on bare columns)

WordPress Plugin Infrastructure

new /pe/v1/self-update REST endpoint — force plugin upgrade from admin
new /pe/v1/debug-update diagnostic — reveals what WordPress sees
new auto_update_plugin filter — future updates without manual clicks
new Docker unpacks wp-plugin alongside ZIP so /plugin-info.json reads live version
fix Plugin header Version matches PE_CATALOG_VERSION define (source of truth)
fix pe_page_has_shortcode detects shortcodes inside Elementor _elementor_data

CI & Infrastructure

new Browser search-workflow regression gate on every deploy (Puppeteer)
new Post-deploy live integration tests — 22 semantic assertions
new Semantic tests for /siblings endpoint (both strategies)
new S2 apparel filter semantic assertions
new S1 tire cascade assertions
new GEMINI_API_KEY + ADMIN_KEY via Gitea secrets
imp Deploy healthcheck 120s → 30s — fast-fail on crash
imp Latency tests split as warnings, not blocking failures
imp My Garage — localStorage-backed saved vehicles + dropdown badge (C3)

Fixes (Misc)

fix Widget AbortController in search useEffect — cancels in-flight fetches
fix Idempotent initApi — skip if unchanged (prevents concurrent render mutations)
fix initApi/initAnalyticsBeacon/setLang wrapped in useEffect with config deps
fix QuickView "View full details" NEVER links to partseurope.eu (always our catalog)
fix QuickView BG i18n + real catalog-url navigation + description lang hint
fix /products no-results message shows once (not duplicated on error)
fix Friendlier error UI + regression test for default /products
fix /tire-sizes cascade — drop DISTINCT (conflict with GROUP BY)
fix API /analytics/event echoes request Origin (not wildcard)
fix Duplicate 'origin' const crashed API startup — renamed to reqOrigin

Documentation, Analytics & Deep Linking

new Project scope page (/scope) with full technical report
new Changelog page (/changelog)
new Plugin icon for WordPress plugins page
fix Deep link query params use pe_ prefix to avoid WordPress conflicts
fix Register custom query variables in WordPress to prevent 404 errors
new Deep linking — link to filtered views from menus, banners, emails
new User documentation page (/docs) with sidebar navigation
new GTM analytics — 6 GA4 e-commerce events pushed to dataLayer
new Stale products automatically hidden from catalog

UI/UX Polish & Dual Pricing

new Dates formatted as DD.MM.YYYY instead of raw ISO strings
new Videos shown as thumbnails in image gallery (not separate section)
new Dual EUR/BGN pricing — legally compliant display with VAT (20%)
new Active filter tags above product grid with individual remove
new Quick clear button in filter sidebar header
fix Subcategory filter collision — selecting "02" in Helmets no longer matches "02" in other categories
new Collapsible filter sections with toggle arrows
new Back button on product detail page
imp All inline styles replaced with CSS classes
imp Translated hardcoded English labels to Bulgarian
fix Mobile layout — toolbar wrapping, filter toggle, responsive grid
imp Consistent spacing scale across all components
fix JSX rendering "0" instead of hiding empty elements
new Widget demo page renamed from /test to /demo

Widget Rewrite & Performance

new URL-based state — every filter, sort, page stored in URL hash (shareable, bookmarkable)
imp Product query optimized from 1200ms to 1ms using CTE approach
fix Light placeholder for products without images (was dark on light theme)
new Widget rewritten to use Catalog API — all dashboard-level filters available
fix Subcategory names loaded from database (were showing as codes)
fix Filter badge count "0" rendering issue

WordPress Plugin & WooCommerce Cart

new WordPress plugin with [partseurope] shortcode
new WooCommerce cart integration — add products directly from catalog
new On-demand product creation in WooCommerce (private, hidden from shop)
new Plugin self-hosted auto-update system (checks every 6 hours)
new Plugin ZIP served from platform for easy installation
new Force update check endpoint for administrators
fix WooCommerce 10.6+ requires Nonce header (not X-WC-Store-API-Nonce)
imp CI automatically rebuilds plugin ZIP on plugin code changes

Catalog Widget (Initial Build)

new Preact widget embedded via Shadow DOM custom element — no CSS conflicts with host theme
new Product grid and list views with pagination
new Brand, category, vehicle, price, availability, and warehouse filters
new Product detail with image gallery, lightbox zoom, specifications, fitments table
new WoodMart theme matching (Rubik font, green primary, square corners)
new Bulgarian and English language support
new Widget test/demo page
imp Widget built automatically during CI via multi-stage Docker build

Catalog API & Data Views

new Public Catalog API with product listing, filters, detail, and vehicle endpoints
new 595 subcategory names populated for human-readable filter labels
new 11 PostgREST views for direct database access
new Advanced filter options: has image, has fitments, warehouse country, shipping alerts, on sale, new arrivals
new Full-text search via tsvector index

Dashboard — Data Browser & Catalog

new Raw Data browser — explore all tables with detail drawers
new Dashboard Catalog page with category filters and product detail
new Category names mapping for human-readable labels
new About page with platform statistics
fix Multiple catalog bugs (images, availability display, vehicle fitment errors, filter issues)
new Active filter chips and category checkboxes in dashboard catalog

Dashboard — Sync Management

new Per-scope sync architecture — each data type syncs independently
new Three sync modes: Full, Incremental Updates, Manual (per-item)
new Full Sync / Full Update modals with scope breakdown and estimates
new Schedule system with day-of-week + time slots (HH:MM precision)
new Schedule tooltips with day-by-day breakdown and next run info
new Per-scope "since" date calculation for incremental updates
new DB Stats table with grouped columns (Table, Last Cycle, Last Update, Schedule, API Endpoints)
new Delete operations with cascade control and type-to-confirm safety
imp Schedule stores UTC, displays local time in UI
imp API Endpoints column with show/hide toggle for cleaner default view

Dashboard — UI & Monitoring

new Live sync status with progress bars and request rates
new History page with colored stat pills per data type
new Logs: Live tail mode (SSE auto-scroll) and Browse mode (pagination + filters)
new Mobile responsive: hamburger menu, card layouts, compact stats
new SSE auto-reconnect with exponential backoff
imp Polished UI: consistent border colors, badge contrast, custom scrollbar
imp Unified border color across entire application
fix SSE streaming through proxy (was buffering entire response)
fix Log page performance — limited DOM entries instead of loading all 19K

Sync Engine — Reliability

imp Batch retry: on foreign key errors, retry items individually instead of losing the batch
imp Rate limiting with 5 retries and Retry-After header support
new Auto-create stub brands when products reference unknown brands
new Detailed error logging with database constraint details
new Fitments: per-vehicle retry with saved/skipped counts
new API key authentication for sync mutation endpoints
new Stale tracking on all tables (products, prices, availability)
imp Exclude stub records (ID ≤ 9) from stale marking and counts

Database & Schema

new Serial ID system with stub records for referential integrity
new Price, availability, and warehouse status history tables
new Auto-migration on API startup (ALTER TABLE ADD COLUMN IF NOT EXISTS)
new Sync schedules table with full/updates dual schedule support
imp Foreign key constraints for data integrity across all relations

Initial Platform

new PostgreSQL database schema for the full Parts Europe data model
new Sync engine — automated data pull from Parts Europe API with pagination and error handling
new API service with health check, sync control, and data endpoints
new Admin dashboard — sync status, controls, and basic monitoring
new CI/CD pipelines for automated build and deploy on git push
imp Merged standalone sync engine into API service

Planning & Research

new Parts Europe API research with live test results
new Architecture and deployment plan
new Data field mapping (1:1 with PE API)
new Project documentation and README

All The Parts