Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.110.md for this page in Markdown format</div>
Version 4
version 4.110
[4.110.0] test(config): set the default PHPUnit
memory_limitto 512M (Advisable-com/ecommercen#373)- Why. A full
composer testrun (all four suites in one process) fatally OOMed at PHP's default 128M while the DI container compiles mid-run (src/Rest/Product/container.php); a clean full run peaks at ~158M. Per-suite runs stayed under the ceiling, so it only bit the all-in-one run. - The fix.
phpunit.xml.distnow sets<ini name="memory_limit" value="512M"/>, so the full suite completes in one process with no CLI override (verified: 6874 tests, 0 failures, default PHP limit still 128M). Developer-tooling only — no runtime code changed. Note: a stale gitignored localphpunit.xmlmissing the line still OOMs — re-copy from.dist(documented indocs/guides/Testing.md).
- Why. A full
[4.110.0] fix(sitemap): reference sitemap-index chunks via
base_url()instead of the storage-disk URL- The problem.
AdvGenerateSitemapsbuilt the chunk URLs written intositemap_index.xmlwith$disk->url($chunkPath), which resolves against the storage disk — an APP_BASE_URL-prefixed path on local-disk tenants but the configuredSITEMAP_S3_URLon K8s/S3 tenants. On S3/CDN-backed tenants the index therefore listed chunk<loc>URLs on the storage/CDN host rather than the site's own canonical host. - The fix. Build each chunk URL with
base_url($chunkPath)so the index always references<canonical-host>/sitemap/sitemap-N.xml, matching the host the index itself is served from. Chunk files are still written to thesitemapdisk and served by nginx/CDN at the same/sitemap/prefix, so the URLs continue to resolve. The adjacent code comment was updated to match.
- The problem.
[4.110.0] fix(admin): replace custom language dropdown with native select
- The bug. The admin header language selector used a custom
<div>-based dropdown driven by JS toggle handlers. On mobile (≤600px) the selector was hidden entirely viadisplay:none, and the sidebar fallback had a CSS typo (color:18pxinstead offont-size:18px). The JS close-on-outside-click and state-sync logic was fragile and unnecessary overhead. - The fix. Replaced the custom dropdown and its JS with a native
<select onchange="window.location.href=this.value">. Removed the duplicate.nav-user-mobilesidebar block and all related JS handlers. Added.nav-mob-lang-selectSCSS with a custom SVG arrow and responsive rules so the selector is visible and styled correctly at all breakpoints. Added arem()SCSS utility function and sethtml { font-size: 16px }as the rem base. Fixed mobile hamburger bar sizing (≤575px) and version badge padding.
- The bug. The admin header language selector used a custom
[4.110.0] refactor(mcp): sharpen tool definitions for the LLM + unify the batch-update shape (MCP SEO Brief #2)
- Why. A three-lens grading of the 17 tool definitions (what the bot sees in
tools/list) flagged a handful of helpfulness gaps. These are AI-facing description/schema improvements — no domain logic changed except theproducts_batch_updatereturn shape (see below). - Write output documented. The shared write-tool note now states that writes return
changed(before/after),warnings(non-blocking, e.g. over-length meta) and the updated object, orno_changes: trueon a no-op — so the bot can tell an applied edit from a no-op and relay warnings. products_batch_updatealigned withcategories_batch_update. It now returns the unifieddata[]+results[]({id|index, status, error?}) +missing[]shape (wasresults[]-only) and its input array param was renamedupdates→items. An existence pre-check routes not-found ids tomissing[]. (Low-risk: MCP clients re-read the schema each session.)langmade discoverable. Everylangdescription now points atget_shop_infofor valid codes and explains that writing to a language the record lacks fails (read first / fall back to default).- Filled gaps. Added per-property descriptions that were missing (
category_id, the fiveupdate_product_contentbody blocks, the sharedid,page/per_page); documented thepaginationblock returned by the three list tools; sharpened thehas_complete_description"raw vs accurate" caveat; clarifiedsearch_productsis_activevsinclude_inactiveprecedence; cross-linkedupdate_product↔update_product_content; notedbrand_idmust be an existing brand; flaggedlist_categories' truncatedmeta_description. - Server instructions now route the bot toward stats/batch tools to cut round-trips, state omit-vs-empty-string semantics, and mention the audit + warnings.
- Why. A three-lens grading of the 17 tool definitions (what the bot sees in
[4.110.0] feat(mcp): category bulk & SEO-audit tools —
categories_stats, filters, batch get/update (MCP SEO Brief #2, Advisable-com/ecommercen#362 #363 #364 #365 #366)- What it is. Categories gain the bulk/filter/stats parity that products already have, so a category SEO audit runs in 1–3 calls instead of ~87 page reads. Brings the MCP tool surface to 17. Purely additive — no existing tool signature changes; new list-output fields are additions.
categories_stats. New tool returning the full audit in ONE call (no pagination):total,active,missing_meta_title,missing_meta_description,missing_both_meta,missing_description,incomplete_description. Optional scope:parent_id,is_active. Backed byCategory\Service::stats()→ a single conditional-SUMaggregate query.list_categoriesfiltering.per_pagecap raised 20 → 200; new server-side filtershas_meta,has_meta_title,has_meta_description,has_description,has_complete_description,parent_id,is_active(maps topublished).has_metauses the same rule as products. Default still returns ALL categories (backward-compatible — does not adoptsearch_products' active-only default).- Lightweight metadata in the list output. Each row now carries
has_meta,has_meta_title,has_meta_description,meta_title_length,meta_description_length,description_length(clean text),description_word_countandhas_complete_description, so a completeness audit needs no per-categoryget_category. categories_batch_get. Reads up to 100 categories by id in one call; returnsdata(theget_categoryshape, in requested order) andmissing.categories_batch_update. Updates name/description/meta for up to 50 categories in one call, best-effort per row (a failing or not-found id never aborts the others); returnsdata(with before/after), per-itemresultsandmissing.- Domain foundation. New
FilterByContentPresencespec + a sharedContentPresenceSqlhelper keep the "content present" rule in one place, reused by both the list filters and the stats aggregate so a filter and its matching count cannot drift.
[4.110.0] feat(mcp):
products_stats+ product completeness metadata (MCP SEO Brief #2, Advisable-com/ecommercen#367)products_stats. Product counterpart tocategories_stats— the same seven audit counts in one call, scoped bycategory_id,brand_id(aliasvendor_id) andis_active.- Metadata parity.
search_productsrows gain the same lightweight metadata fields aslist_categories(has_meta_title/has_meta_description,meta_*_length,description_length,description_word_count,has_complete_description), andsearch_productsgains ahas_complete_descriptionfilter.
[4.110.0] feat(domain):
has_complete_descriptionfilter +incomplete_descriptionstat (raw-length, MCP SEO Brief #2, Advisable-com/ecommercen#371)- What it is. A configurable "complete description" dimension for categories and products. A description is complete when present and reaching a length threshold, incomplete when present-but-short (disjoint from "missing").
- Accurate per-row, fast server-side. The per-row
has_complete_descriptionshown in the list output is the accurate clean-text flag (DescriptionMetrics, strips HTML). The server-side filter and theincomplete_descriptionstat use a faster raw-CHAR_LENGTHSQL approximation — so a borderline row can classify slightly differently between the two, by design (documented). - Configurable threshold.
APP_MCP_DESCRIPTION_COMPLETE_MIN_CHARS(default 120) feeds both the domain Services (DI-bound) and the MCP projection, so all three agree on the number. - Note. The category
updated_sincerequest from the brief was dropped (no timestamp column onshop_product_category).
Notes
- [4.110.0] Requires
npm run admin-production- The admin nav redesign and the native language-select fix changed admin SCSS (
assets/admin/scss/_admin_nav_redesign.scss,_mixins.scss,_root_vars.scss,_update.scss) and admin views. Upstream ships the rebuiltpublic/ui/admin/dist/bundles in this release, so a standard deploy needs no build step — but clients that compile their own admin assets must runnpm run admin-productionto pick up the change. The storefront bundle is unchanged, sonpm run productionis not required.
- The admin nav redesign and the native language-select fix changed admin SCSS (