SectionMate MCP - full changelog

The readme.txt changelog keeps only the most recent releases, because the
WordPress.org readme parser truncates that section at 5000 characters. The
complete history lives here.

= 2.1.28 =
* **The two external services this plugin uses are now documented**, in a new `== External services ==` section of the readme: the Wikimedia Commons API behind `search_placeholder_images`, and Google Fonts. Each entry says what the service is for, what is sent, when it is sent, how to avoid it, and links to that provider's terms and privacy policy. The directory requires this disclosure for any outbound call, and the plugin had been making both without it.
* **A Privacy claim was wrong and is corrected.** The readme said the plugin "does not add third-party scripts, fonts, or trackers to your pages". Scripts and trackers were true — 2.1.26 removed the last remote script tag. Fonts were not. `enqueue_page_fonts()` hooks `wp_enqueue_scripts` and enqueues a `fonts.googleapis.com` stylesheet for every family a compiled page names, so the visitor's browser fetches it from Google and Google sees the visitor's IP and user-agent. The admin screen and the logged-in Inspect overlay load IBM Plex Mono the same way. Google Fonts is the documented exception to guideline 8 and stays, but a readme that denied it was the more serious problem: the disclosure exists so site owners can meet their own obligations, and this one told them there was nothing to disclose.
* **Images are imported into the Media Library, not hotlinked.** The skill doc carried a ranked table of four placeholder hosts — Wikimedia Commons, Unsplash CDN, LoremFlickr, picsum — with URL patterns for each, so an agent with no library match reached for a third party by default. That table is replaced by one ordered path: `search_media` for what the site already has, then `search_placeholder_images` followed by `import_media`, then ask the user. The plugin ships no image host and endorses none; if a user names a service they want, that is their URL and it goes through `import_media` like any other. Finished pages now serve their own images, which also survives the host going away.
* Following from that, the plugin contains **no image-host URLs anywhere in its source**. The picsum, Unsplash, LoremFlickr and Wikimedia example URLs are gone from the skill doc, from the `search_placeholder_images` response text, and from thirteen selftest fixtures, which now use stable local `/wp-content/uploads/…` paths — stable rather than `home_url()`-derived because the drift gate compares compiled output byte for byte and a site-dependent string would fail it everywhere except the machine that locked it. The only external hosts left in the source are the Google Fonts endpoint, the Commons API endpoint, and licence and policy links.
* The Wikimedia request identified itself as `SpectraCompiler-MCP/1.82` — a product name dropped at 2.1.0 and a version 46 releases stale. It now sends the real name and the running version.
* Selftest drift baseline re-locked for the changed fixture URLs. No compiler behaviour changed: the fixtures exercise the same nodes and fields, and only the `url` strings differ.

= 2.1.27 =
* Verified against **Elementor 4.2.3** as well. Nothing needed changing: the selftest is clean on all 23 fixtures with the baseline still locked at 4.2.2, meaning the compiled trees are byte-identical under the new version, and all 55 lab runs reproduce to 0.00. The source diff from 4.2.1 is 19 PHP files, every one of them Atomic-editor, onboarding, admin menus, promotions or vendor autoload — none in the classic Container/widget lane this compiler targets. The two behaviour changes visible in it are an Atomic URL prop type swapping `wp_http_validate_url` for `filter_var`, and Atomic style validation logging and skipping an invalid property instead of throwing (their "invalid style properties prevent pages from being published" fix).
* That release also lists "improved code security enforcement in template handling", which is exactly the kind of change that could quietly strip a `code` node — so that path now has an end-to-end test instead of an assumption: compile, save through the plugin's own writer, clear Elementor's CSS and element caches, fetch the rendered front end, and assert the `<style>`, the base64 `<script>` and the raw HTML fragment all arrive, with the script decoding byte-for-byte to what was authored. It passes on 4.2.3.
* The gap that test closes is worth naming: twelve design labs use a `code` node and **every one of them is CSS-only**. Not one uses `code.js`. The pixel sweep would have gone on reporting +0.00 across 55 runs while the script path was dead, because nothing it renders depends on it.
* Two things the test itself got wrong first, both worth knowing for the next one: Elementor's script wrapper quotes the base64 payload with `"` while the Spectra wrapper uses `'`, so a regex written for one silently fails on the other; and Elementor caches both the generated CSS file and the rendered element HTML per post, so an uncleared fetch returns the *previous* run's page — which passed a script check against a payload from ten minutes earlier.
* Verified against **Spectra (UAGB) 2.20.2**. Nothing needed changing for it: all 40 test suites pass, and all 55 design-lab runs across the four backends reproduce their previous score exactly, to 0.00. The source diff from 2.20.0 is 15 files, of which the only functional changes are cache-busting by file mtime on two enqueues and a `manage_options` check on an admin setting; the image block and the CSS helper changed by docblock only. Worth recording that the editor bundle cannot be usefully diffed — it was rebuilt with different webpack module ordering, so the whole file shifts and a textual comparison says everything changed. The behaviour had to be measured instead.
* **A CSS class multiplied on every Elementor round trip.** The decompiler reads one `_css_classes` string into two fields — `class`, and `advanced.css_classes` via `read_advanced` — and the compiler concatenated every source it knew about, so `hero-band` came back as `hero-band hero-band`, and each further decompile → edit → recompile added another copy. Harmless to CSS, which is exactly why it lasted; not harmless to read, to diff, or to any selector that counts classes. The compiler now merges and de-duplicates while preserving order, which also cleans up pages that already carry the duplicates rather than only stopping new ones. The decompiler additionally stops repeating the class under `advanced` when it is already the node's own `class`, so the JSON an agent reads says it once.
* **An Elementor image widget could name one file and point at another.** When the attachment resolver matched a remote URL to something already in the media library, it wrote the local URL to `src` — but the emitter reads `url` first and only falls back to `src`, so the saved widget carried the original remote URL beside the id of the local attachment that had replaced it. Elementor renders from the id, so the page looked correct while its own data contradicted itself: wrong in the editor's media field, wrong on export, and a round trip that never settled, because the second pass resolved the id and rewrote the url to match. The resolver now sets both.
* Neither bug came from the Spectra update. Both were found by re-running the selftest after it, and both had been sitting under a **drift baseline stale since 2026-08-03 and plugin 1.96.4** — thirty releases of intended change ago. A gate that fails on all 47 fixtures cannot report anything, and the three genuine round-trip failures underneath were invisible in the noise. This is the second time in two releases that a real defect turned out to be hiding behind an instrument nobody trusted; the first was lab 010's font substitution, dismissed in its own scorecard as an environment condition.
* The baseline is re-locked against 2.1.27 / UAGB 2.20.2 / Elementor 4.2.2, with the suite green: 0 drift failures, 0 round-trip failures, 0 errors, across 23 fixtures on all four backends.
* New suite `elementor_roundtrip_stability_unit.php` holds both fixes down: three full cycles must not grow a class, all four spellings agents use for a class (`class`, `advanced.cssClass`, `advanced.css_classes`, `advanced.css_class`) must merge to one copy each in order, every selftest round-trip fixture must settle, and a resolved image's `url` must match the attachment id beside it.

= 2.1.26 =
* **Remote animation libraries removed.** `code.libs` used to write `<script src>` tags for GSAP, ScrollTrigger, ScrollTo, Lenis, Three and Typed from a public CDN, behind an admin switch that was off by default. Those URLs are gone — all three copies of the map, in the Spectra code builder, the Elementor code widget and the decompiler's reverse lookup — and so are the "Remote animation libraries" setting, the `sectionmate_mcp_allow_remote_libs` option and the `sectionmate_mcp_allow_remote_code_libs` filter. WordPress.org's Plugin Check flags a hardcoded third-party script URL whether or not anything can reach it, and those six lines were 18 of the plugin's 18 errors. The guideline behind the check is a good one: a page builder has no business deciding that your visitors' browsers should talk to someone else's server. The plugin now writes no remote script tag of any kind.
* `libs` is still accepted on a `code` node, and is now reported as dropped on every backend, naming what it dropped and where to put the library instead (enqueue it from your own theme or a small site plugin, then guard for it in `code.js`). A field that stops working in silence is the bug this codebase keeps finding, so removing one quietly was not an option. CSS and your own `js` are untouched.
* Pages that already carry those scripts keep them. A `code` block read back from a page becomes a shell whose stored markup is preserved byte for byte, so the tags still load and still run. Changing `libs` on such a shell used to rebuild the whole block through the code builder — which, now that the builder writes no script tags, would have deleted them as a side effect of editing a neighbouring field. The stored markup is left exactly as it is and the attempted change is reported instead.
* The skill guide loses its GSAP example and gains the plain-CSS and `IntersectionObserver` equivalents, which is what the labs were using anyway. A staggered reveal is `transition-delay` on `:nth-child()`; a scroll trigger is one observer adding a class.
* **Spectra Legacy set text in a serif whenever a webfont failed to arrive.** UAGB writes `font-family: "Inter"` with no generic after it, so with the Google font unavailable — offline, blocked by a privacy or GDPR plugin, or simply still in flight on first paint — the browser reached for its own default, which is a serif, while Elementor, Gutenberg and Spectra V3 all wrote a stack and fell back to a sans. Identical clean JSON therefore laid out differently, and not subtly: lab 010's stat copy set in two lines where it needs three and left its card 29px short, because Times sets narrower than Inter at the same size.
* Every family the compiler writes on Legacy now gets a generic fallback — serif, sans-serif or monospace, chosen from the family name by the same helper the other three backends already used. The fallback is added as a rule of the plugin's own, on the leaf element UAGB paints (`.uagb-heading-text`, `.uagb-desc-text`, `.uagb-highlight`, `.uagb-buttons-repeater`), with the block class repeated so it outranks UAGB's rule whatever order the two stylesheets load in. The attribute UAGB reads is left as the bare family name, because it builds the Google Fonts request from that same value and would ask for a whole stack as one family. It rides on the page-fonts pass that already existed for Spectra V3. All sixteen measured boxes on lab 010 now agree across all four backends at 1440 and 1600.
* Worth recording, because the diagnosis was wrong twice: this was written up in 2.1.24 as an unexplained wrapping bug, on the grounds that everything deciding the wrap measured identical to Gutenberg — same inner element, same 14px, same 28.112px line-height, same 324px box, same 103 characters, overflow visible. All of that was true. The thing that differed was the face the text was painted in, and no measurement of the box could see it. `flex-shrink: 0` was tried and changed nothing, which correctly ruled out flex compression and incorrectly suggested the cause was still in the layout.
* Also invisible: the same lab's Legacy scorecard had already described this exact mechanism a release earlier, and dismissed it as "an environment condition on this site, not a compiler defect". The compiler chooses what font-family string to write, so it was always a compiler defect.

= 2.1.25 =
* **`bgPosition` as an array compiled to the literal string "Array" on Elementor and Gutenberg.** The field is documented as a focal point and the docs lead with the array form — `[0.5, 0.3]` — alongside `{x,y}`, '50% 30%' and keyword pairs. Both Spectra backends ran every shape through the shared normaliser; these two cast the value straight to a string, so an array produced the text `Array` in the background-position slot plus a PHP warning for every node that used it. The documented shape worked on half the backends. Both now normalise arrays to a percentage pair; strings are still passed through byte-for-byte, so a keyword survives a round trip as a keyword.
* The obvious-looking version of that fix was wrong and is worth recording: routing Elementor's custom positions through its own `_position: 'initial'` plus the `_xpos`/`_ypos` sliders — the shape Elementor's UI stores — measured **worse on three labs** (012-energy +0.66, 02-imagine +0.15, 011-phone +0.06). Elementor emits `background-position: initial` verbatim, `initial` resolves to `0% 0%`, and every custom position became a top-left crop while the sliders went unread. The percentage string it already accepted was correct all along.
* Design-lab 016-4columns added, all four backends, **11.24 and identical to 0.01 on every one**, with `box_parity` reporting every box in agreement at 1440 and 1600. It is the first lab whose colours are computed rather than written: each of eight preset cards takes its gradient from the artwork on it, hue landing within 7.6 degrees of the reference (mean 3.8). Hue is a saturation-weighted circular mean rather than the most saturated pixel — a cool blue plate with one warm bloom otherwise returns a tint 94 degrees off — and lightness is pinned rather than scaled, so an almost-black image still yields a card that reads as a card.
* Tooling: `mobile_audit.py` had `pricing` mapped to the class prefix `pr-` while that lab uses `pc-`, so its selector matched nothing and the lab reported "clean" without a single element being measured. Corrected, and 05-pricing genuinely passes. Found by collision when 016 claimed `pr-`.

= 2.1.24 =
* **Mobile defaults, so a desktop-tuned page does not fall apart on a phone.** Nothing here designs for mobile — it stops pages breaking. Measured at a true 390px before any of it existed, three of four backends scrolled sideways on lab 015, a tilted photo kept its rotation and its -229px margin on all four, and lab 013 put a dot 66px off the left edge. Four defaults now apply: mobile padding is **capped** at 40 top/bottom and 16 either side rather than merely halved (120px of side padding halves to 60, still a third of a 390px screen); a row whose children's declared widths cannot fit the viewport stacks, as does any row of two or more boxes unless it is hugging; negative margins and oversize widths — the things that make elements sit on top of each other — are neutralised on mobile only, leaving the desktop values untouched; and a `code` node's CSS is wrapped in `@media (min-width: 782px)` unless it sets `allBreakpoints`, because decorative CSS is written against a wide canvas and all of it used to survive onto the phone. All 24 lab × backend combinations now report no overflow, nothing off the edge, and no surviving negative margins or transforms. Desktop output is unchanged.
* **Forced line breaks are no longer written into text, and every compile says so.** A `<br>` or newline in `text` reproduces a reference design's line pattern at exactly one viewport width and is wrong at every other, because that break was never a design decision — it is where that font at that size ran out of room in that container. Narrower, the text breaks twice and strands a word; wider, it breaks early against a half-empty container; on a phone it costs a whole line each time. All four backends now emit a `forced_break` advisory naming the count and quoting the first break in context. It advises rather than strips: an adopted page's `<br>` has to survive a round trip, and a break that belongs to the *content* — a postal address, a verse, a name over a title — is legitimate and stays. The skill gains a "Never force line breaks" rule and the flagship example no longer demonstrates one.
* Skill: the portable way to reproduce a reference's line pattern is to constrain the **measure**, not the string — a `container` with a `width` (the only route Spectra Legacy and Spectra Blocks V3 honour, neither having a width control on its text blocks), or `width` on the text node itself on Elementor and Gutenberg. Both stay proportional as the viewport changes.
* Tooling: `wrap_width.py` computes the width that reproduces a given set of design lines, by measuring the type in a real browser and reporting the band between the widest line and the narrowest "line plus the next line's first word". It also reports when **no** width can work — which caught a review quote whose middle line is shorter than the line above it, a break that greedy wrapping can never produce and so was never a wrap at all.
* Tooling: `mobile_audit.py` measures a page at a true 390px through a sized iframe, because Chrome will not open a headless window narrower than ~500px on Windows and silently measures 500 instead — reporting a page that fits when it does not.
* Fixtures: 47 forced breaks removed from 18 lab specs, the great majority of them body copy chopped at the reference's line ends rather than deliberate display breaks. Where the line pattern was part of the composition it was restored by measure; eight of the eleven affected labs return to their previous score exactly.
* Known, unfixed: on Spectra Legacy only, `010-more-data`'s stat-card copy lays out in two lines where it needs three and spills out of the card. Everything that decides wrapping measures identical to Gutenberg — same inner element, Inter 14px, line-height 28.112px, 324px box, same text, overflow visible — yet Legacy reports a 56px box against Gutenberg's 84px. `flex-shrink:0` changes nothing, so flex compression is not the cause. The forced breaks had been hiding it, since a three-line block is three lines however it would have wrapped.

= 2.1.23 =
* Docs: how to tilt something. Lab 015 asked for a photograph rotated behind another one, and no backend can take that as clean JSON — Elementor keeps rotation behind its Advanced transform controls and Gutenberg, Spectra Legacy and Spectra V3 have no attribute for it anywhere in their schemas. The portable answer is a class on the node and the transform in a `code` node, which renders identically on all four; the skill now says so, along with the two things that catch people out: a rotated box still occupies its unrotated space, and the box's aspect ratio decides how `bgSize:"cover"` crops the photo.
* Tooling: `font_pick.py` takes `--fonts` (or `--serif`) so a reference that mixes families can be measured against the right shortlist. Lab 015 sets its headline and stat numerals in a high-contrast serif and everything else in a grotesque; measuring the serif against the sans shortlist only reports which sans is least wrong. Still one run per family.
* Tooling: `box_parity.py` no longer reports a font as substituted on the strength of `document.fonts.check()`. That call answers for every face matching the family, so a page which declares the family twice — Spectra Legacy gets one enqueue from this plugin and another through the theme — reports false while its text is demonstrably painting in the right face. The canvas measurement against both generics decides now; the check result is kept as a hint.
= 2.1.22 =
* **`fill` collapsed its neighbour on Elementor.** It is the commonest asymmetric layout there is — one column of a stated width, one taking whatever is left — and on a 1044px row holding a 580px column and a `fill` column, the fixed column came out **0px wide**: its paragraph wrapped one word per line and the page rendered four times too tall, from JSON that is correct on Gutenberg. Elementor's Size → Grow preset is `--flex-grow: 1; --flex-shrink: 0` and a container's own default is `--width: 100%`, so the filling column's flex basis was the entire row and it refused to give any of it back. `fill` now compiles to Size → Custom with grow 1 **and shrink 1**, with the width control set to `auto` so the basis comes from content. An explicit `width` on the same node still wins. The decompiler reads both shapes, so older pages and hand-set Grow containers still round-trip.
* **`fill` did nothing at all on Spectra Legacy.** No attribute, no advisory: the container block has no flex-grow control anywhere in its schema, so the field compiled to silence and the row split evenly instead — 498/498 where the author asked for 580 and the rest. It now says so and names the two shapes that work on every backend.
* The no-silent-drops probe gained `fill`. It was never on the list, which is how a field that renders on three backends and vanishes on the fourth survived fourteen labs.
= 2.1.21 =
* **`bgOverlayGradient` did nothing on Elementor or Gutenberg.** A scrim over a photo band rendered on Spectra Legacy and V3 and simply did not exist on the other two — Elementor reported nothing at all, and Gutenberg not only dropped it but applied its own 30% flat dim and reported *that*, telling the author they had omitted an overlay they had in fact written. Gutenberg now maps it to core/cover's `customGradient`, which takes the CSS string whole and so carries any number of stops. Elementor maps it to its overlay group's gradient controls, and where the gradient has more than two stops — more than that control can hold — it says so and names the field instead of flattening it to a solid in silence.
* **`bgGradient` did nothing on Spectra V3.** A gradient only ever reached that backend if it was smuggled through the `background` field, which its colour branch happens to detect; the field named for the job was never read. Measured on a photo card whose top scrim should be rgb(23,41,70), the other three painted it and V3 showed the bare photo at rgb(141,142,147) — a 3.7-point score gap that no field probe saw, because the field survived compilation and was simply never looked at.
* **`margin` with `auto` sides did nothing on Elementor or Legacy.** It is the CSS way to centre a fixed-width element, and Gutenberg and V3 pass it through. Elementor's margin control appends its unit, so `auto` became `margin-left: autopx` and the browser discarded it; Spectra's margins are numbers sharing one unit and its block has no align-self at all. A 348px rule meant to sit centred under an 874px column measured at x283 on those two and x546 on the other two. Elementor now maps auto sides to Align Self (`_flex_align_self`), which centres the element without touching its siblings; Legacy cannot express it and now says so, naming the wrapper shape that works everywhere.
* **`overflow` is now honoured on Gutenberg.** core/group has no overflow control, so the field was reported as needing a code node. That was true of core and false of the plugin, which already ships `assets/responsive.css` for exactly this kind of gap: `hidden`/`clip` and `auto`/`scroll` now write `.apcm-clip` / `.apcm-scroll`. A rounded card whose children have square corners clipped them on three backends and let them poke out on the fourth. A class rather than an inline style, because the editor validates a block's saved markup against what save() would produce and a declaration with no attribute behind it makes the block "invalid content". An overlay on a *nested* container still cannot apply — that compiles to a group, where the image and the gradient are the same CSS layer — and now explains itself rather than vanishing.
* The no-silent-drops probe gained the two gradient fields. It had been reporting a clean sheet while three of the four bugs above were live, because a field it does not list is a field it cannot miss.

= 2.1.20 =
* **The layout normalisation is now shared by all four backends.** It lived in the Spectra Legacy engine alone, so the same clean JSON was normalised on one backend and taken literally on the other three - a whole family of divergences from one cause. The clearest case: `childrenWidth:"auto"` shrank a container child to 69px on Legacy and left it 1362px wide on Elementor, Gutenberg and Spectra V3. Both of those backends mapped the field correctly (Elementor wrote `--width:auto`, V3 wrote `selfStretch:fit`) and were then cancelled by the parent's `align-items: stretch`, because only Legacy filled in the `align:"flex-start"` that makes a shrink-to-content column mean anything. Content-row defaults, sized-column alignment, hero vertical centring and the image-plus-text legibility overlay are now identical everywhere, and every inferred default is reported on every backend.
* **A column container's cross axis is now stated explicitly.** CSS, Elementor and Legacy default it to `stretch`; WordPress writes `align-items: flex-start` for a vertical flex container when justifyContent is absent, and Spectra V3 defaulted to `flex-start` too. So children hugged their content on two backends and filled the row on the other two, from identical JSON. That is not a box-metrics footnote: a heading's `align` is text-align, and a box shrunk to its own text has nothing left to centre inside, so `heading.align:"center"` rendered centred on two backends and hard left on the other two. Measured at 1440px, a heading inside a plain column container: 1342px wide on Elementor and Legacy, 355px on Gutenberg and V3.
* **`align` on a column whose children fill the width now compiles to `stretch` on all four, and says so.** `childrenWidth` is the field that hugs; `align` only places what is already narrow. Elementor and Legacy enforce that split in their own CSS, so a hugging `align` over full-width children does nothing there while the other two shrank the children to their text. The advisory names the fix (`childrenWidth:"auto"`, or a `width` on the children) and stays quiet when the children already hug, or when the alignment was one the compiler itself inferred.
* Gutenberg: `childrenWidth:"auto"` did nothing under any container that also set `innerWidth`. Such a container compiles to a constrained layout, where `selfStretch:fit` emits no CSS at all and there is no cross axis to align on - WordPress places constrained children with `margin-inline: auto !important`, which no inline style can override. Hugging children now get an explicit `fit-content`, and a rail that has to place them left or right gets a flex wrapper instead of the constrained layout. A child with an explicit `width` was centred by the same rule: a 700px column inside a 1440px rail sat at left 361 here and left 30 on the other three.
* Gutenberg: text blocks kept the theme's vertical margin when the JSON did not mention one. The other three all reset it in their own CSS (`.elementor-heading-title{margin:0}`, `.uagb-heading-text{margin:0}`), so with Astra's 1.6em three margin-less paragraphs in a `gap:0` section measured tops of 30/87/168 here against 30/55/104 on Legacy and V3 - a drift that compounds down the page and is invisible in any single element's box. Only the axis the author left unspecified is filled, so `marginBottom` still wins.
* Elementor: every grid earned a `childrenWidth:"auto"` it never asked for. A grid is built by handing the whole node to the container compiler with an empty children list, which reads as a content row. Grids now skip the flex rules, as they always have on Legacy.
* New advisory: text nodes with no `lineHeight`. Elementor's frontend CSS pins `.elementor-heading-title{line-height:1}` while the other three inherit the theme (~1.2-1.6), which makes it the one field whose default is genuinely unportable - measured 20px against 25px for a single 20px line, and 11px per line on a 44px headline. The compiler does not pin a default, because inventing one would restyle every existing page on three backends; it reports once per compile, and only for headline-scale or certain-to-wrap text, where the drift is visible.
* New verification tool: `scripts/verify/box_parity.py` reads element boxes straight out of the DOM and compares them across all four backends at more than one viewport, using the backends as each other's oracle rather than a reference image. Every bug above was found with it and none were visible to the pixel diff, which font substitution dominates, or to the no-silent-drops probe, which each field passed by surviving compilation and landing somewhere with no effect.

= 2.1.19 =
* Spectra Blocks V3: `innerWidth` on a root section is now honoured. It used to be dropped with an advisory telling the author to nest a constrained container, so the same clean JSON boxed the content on Elementor, Gutenberg and Spectra Legacy and left it full-bleed on V3 - invisible at a 1440px viewport, and plain at 1600px, where the three measured 1200 and V3 measured 1600. A root with `innerWidth` now wraps its children in a width-constrained, auto-margined container that keeps the band's background full-bleed. Reported by the plugin's author.
* Spectra Blocks V3: `bgSize`, `bgRepeat` and `bgPosition` were written to `background.size` / `.repeat` / `.position`, which Spectra does not read - it reads `backgroundSize` / `backgroundRepeat` / `backgroundPosition`. All three silently fell back to Spectra's own defaults, which is why it went unnoticed: cover / no-repeat / centre are what most pages want. `bgPosition` is also parsed to the numeric `{x,y}` fractions Spectra multiplies by 100, so a keyword can no longer be quietly re-centred.
* Gutenberg: a section with a background image compiles to `core/cover`, and when it also sets `innerWidth` its children go into a nested Group. That wrapper hardcoded a column and dropped the section's `gap`, so core's constrained-layout default put a 24px margin between children that had been explicitly set to 0 - every child of a photo band sat 24px lower than on the other three backends. The wrapper now carries the section's direction, justify, align and gap.
* Gutenberg: a `core/cover` section with no `minHeight` is given 100vh here, while the other three backends size the band to its content. That default stays, but it now says so.

= 2.1.18 =
* Spectra Legacy: a `bgPosition` given as a CSS string **crashed the front end**. Spectra multiplies that attribute by 100 to build its CSS (`$position['x'] * 100`), and the compiler passed the string straight through - `(array) 'center top'` gives x = "center top" - so PHP 8 raised "Unsupported operand types: string * int" and the page white-screened. A percentage string did not fatal but was just as wrong: '72% 48%' is leading-numeric, so it warned and evaluated to `background-position: 7200%`. `bgPosition` is a CSS string everywhere else in the spec, so that was the shape actually arriving. It is now parsed to Spectra's numeric x/y fractions - keywords in either order, one or two values, percentages, fractions and `{x,y}` objects all accepted - and the result is always a float, so nothing non-numeric can reach that multiplication again.
* Docs: the button guidance now warns that many themes force `text-transform: uppercase` on buttons (Elementor's own CSS resets it, the other three backends do not), and that `transform: "none"` on the button item is the fix.
= 2.1.17 =
* Gutenberg: grid cells did not stretch to the row height. `core/column` fills the row, but the styled group inside it is content-height, so a card with less copy ended short and its background stopped early - the third card of a three-up finished 28px above its neighbours. `gridAlignItems` had only ever been wired up in the Legacy engine, so the field was dropped in silence here; cells now stretch by default (and for an explicit `stretch`), `flex-start` keeps content height, and `center` / `flex-end` - which `core/columns` cannot express - are advised instead of dropped.
* Gutenberg: `font` was written into the inline style as a bare family name. Core serialises that value verbatim, so a family whose name is not a valid CSS identifier ("Baloo 2") produced `font-family:Baloo 2` and the whole declaration was dropped by the parser. A bare name also names no generic, so a family that fails to load falls back to the browser default *serif* rather than a sans - which is what happened on a site where another plugin registers its own local copy of the same family through the core Font Library and that face wins the match. The value is now the full quoted stack with a matching generic, e.g. `"Inter", sans-serif`.
* Spectra Blocks V3: corrected a docblock that claimed `padding` also accepted CSS order. It never has - the project order is [top, bottom, left, right] everywhere - and a comment promising otherwise is how the next change introduces a real bug.

= 2.1.16 =
* Gutenberg: a `grid` with more cells than `columns` came out as a single row, the same defect fixed for Elementor in 2.1.15 but in a different shape - `core/columns` cannot wrap, so every `core/column` landed on one line and `columns: 2` with four cells rendered four columns. A multi-row grid now emits one `core/columns` per row inside a group, with the row gap on the group and the column gap on each row. A short final row is padded so its columns keep their width instead of stretching. Grids whose `columns` equals the cell count are unchanged.

= 2.1.15 =
* Elementor: a `grid` with more cells than `columns` came out as a single row. The cells were sized 100/columns percent but the row was pinned `nowrap` (added to stop a rounding overflow), so `columns: 2` with four cells rendered four narrow columns instead of 2x2. The row now wraps when it needs rows, and cell widths subtract the gutters so they cannot overflow into one-per-line. Grids whose `columns` equals the cell count are unchanged.
* Elementor: writing a page's tree cleared the CSS cache but did not rebuild it, so a page written outside a normal front-end request could keep serving CSS generated from the PREVIOUS tree - new markup, old styling. The post CSS is now deleted and regenerated on write.

= 2.1.14 =
* Spectra Blocks V3: the `zIndex` advisory said "set z-index from a code node" without saying what you are competing with. Spectra gives EVERY container `position:relative;z-index:2`, so a code node using z-index 2 or below ties and loses on DOM order - text layered over a photo was covered by it. The advisory now names the default and says to go above it.

= 2.1.13 =
* Gutenberg: a container's background image was written with a size but no position, so a group used the CSS initial value of top-left while Elementor and both Spectra backends centre a cover background. The same clean JSON produced a visibly different crop on one backend out of four. Position and repeat are now written; an explicit `bgPosition` still wins.
* Spectra Blocks V3: a container given both `background` and `bgImage` rendered as a flat colour with the photo hidden behind it. Spectra paints a container colour on the ::before layer, which sits ABOVE the background image. The colour is now dropped when an image is present, and the compiler says so - `bgOverlay` remains the field for a tint over the photo.
* Spectra Legacy: the same pair is also unsupported there (UAGB treats a container as either a colour or an image), and was discarded without comment. It now advises.

= 2.1.12 =
* Google Fonts were requested without the italic axis, so any copy using `<em>` or an italic style rendered a browser-synthesised oblique - a sheared upright - instead of the family's designed italic. On a face with a true cursive italic that is a different alphabet; on Instrument Serif it is a different ampersand and text 5% narrower. The request now asks for both axes. Families that have no italic are unaffected: Google Fonts serves them normally rather than failing the request.
* This only ever showed on Gutenberg, Spectra Legacy and Spectra Blocks V3. Elementor separately enqueues every weight and italic through its own font system, which masked the bug on that backend.

= 2.1.11 =
* Spectra Blocks V3: a `gap` on a grid never reached the grid. The value was written to `layout.gap`, a Spectra key that only applies to flex layouts; a grid is laid out by WordPress core, which reads `style.spacing.blockGap`. Every V3 grid therefore sat on the theme's block gap - 24px where the design asked for 12 - while the compiler reported the gap as applied. The string form of `blockGap` is now always written. (The object form, which caused Spectra PHP warnings, is still never written.)
* Spectra Blocks V3: the two-axis gap advisory said it had used the column value. It had not applied anything at all. It now states plainly that both axes get the single value, and how to set the other axis.

= 2.1.10 =
* Gutenberg: responsive font sizes never reached the desktop size. `sizeMobile` / `sizeTablet` compiled to `clamp(min, 4vw, max)`, where the middle term was a constant unrelated to either end - 4vw is 57.6px at a 1440 viewport, so the ramp only landed on the desktop size when that size happened to be under 57.6px. A 49px headline was right by accident; an 86px figure rendered at its 77px mobile size on every desktop. The preferred term is now the line through (480px, small) and (1024px, desktop), so both ends are exact and every width above 1024px gets the desktop size.
* Gutenberg: a single clamp() has two stops, not three. When `sizeTablet` is set and is not what the ramp produces at 768px, the compiler now says so instead of dropping it in silence.

= 2.1.9 =
* Elementor: `width` / `widthUnit` on a text, image or button node was dropped. Elementor exposes the control on every widget (Advanced > Layout > Width = Custom), so a paragraph asked to be 255px wide just filled its column instead, in silence. Now written, responsive variants included.
* Spectra Legacy and Spectra Blocks V3 have no width control on their text blocks. They no longer drop `width` without comment - the advisory names the field and points at the wrapper container that can carry it.
* Field-survival probe: fields are now probed per node type rather than on containers only, and modifier fields such as `widthUnit` are paired with the field they modify instead of being reported as dropped for doing nothing on their own. Probing only containers is what hid the Elementor gap above.

= 2.1.8 =
* Spectra Blocks V3: `widthUnit` was ignored on containers, so `width:8` + `widthUnit:"px"` compiled to `width:8%` — a small accent chip became a wide bar. Explicit units now win; a bare number still reads as a percent, or px above 100.
* Spectra Blocks V3: `shadow` on a container is now mapped to the block's native `boxShadow` attribute instead of being dropped with a "no shadow support" note. spectra/container has carried that attribute all along.
* Spectra Blocks V3: stopped warning that container borders do not render. They do — spectra/container declares border support and paints it. An advisory nobody can act on is as bad as silence.
* Spectra Legacy: `childrenWidth:"auto"` and a child's explicit `width` contradict each other, and Spectra settles it with `width:auto !important` — the child sizes to its content, so an empty decorative child collapses to nothing. Both fields are still emitted as written, but the compiler now names the conflict instead of letting the element vanish in silence.

= 2.1.7 =
* Spectra Legacy: a full-width root section with no `innerWidth` silently lost its flex layout on the front end — Spectra styles a root's direction/alignment/gaps through an inner wrapper it only outputs when `innerWidth` is set, and otherwise caps content at its default 1200px rail. The markup was right; the styling landed on a selector that did not exist. The compiler now tells you, and suggests setting `innerWidth`.

= 2.1.6 =
* Gutenberg fix: `gap` on a `grid` node never reached core/columns — it was only wired up for containers — so a grid fell back to the theme's default block gap. A five-column row asking for `gap: 0` rendered with ~26px between columns and the last column squeezed to absorb it.

= 2.1.5 =
* **Elementor fix: `shadow` never rendered.** The box-shadow value was written but not Elementor's group toggle, which decides whether the shadow is drawn at all — so every shadow authored for an Elementor page, on containers and images alike, was silently inert. `position: "inset"` now maps across too.
* Elementor fix: a gradient that its two-stop background control cannot express (three or more stops) degraded to a flat first colour in silence. It now says so, naming the field and the supported shape.
* Elementor: two-stop **radial** gradients are now compiled natively instead of falling back to a solid.

= 2.1.4 =
* **All four backends now tell you what they dropped.** Elementor, Gutenberg and Spectra Legacy join Spectra V3 in emitting an advisory whenever a documented field cannot be honoured — naming the field and suggesting an alternative — instead of discarding it in silence. Read `advisories[]` on any compile whose result looks wrong.
* Spectra Legacy gained advisory support (`get_advisories()`); it previously reported warnings only.
* Gutenberg now says so when `sizeTablet` / `sizeMobile` cannot be approximated (the clamp needs a desktop `size` larger than the responsive one) rather than doing nothing quietly.
* Spectra V3 now advises on a `contentWidth` it cannot honour, while staying quiet on `alignfull`, which V3 roots already do.
* New `scripts/verify/field_survival_probe.php` measures, per backend and per field, whether a value survives compilation — so the unmapped-field lists stay evidence-based.

= 2.1.3 =
* Spectra V3 fix: containers with no padding in your JSON inherited Spectra's default padding, nudging content inward and compounding with nesting depth.
* Spectra V3 fix: the documented `border` object (`{style, color, width}`, with `width` as a number or a per-side array) was ignored entirely — only the flat `borderWidth`/`borderStyle`/`borderColor` keys were read, so card outlines and hairlines produced nothing.
* Spectra V3 fix: a CSS class on a `buttons` node or on a heading/paragraph node never reached the block, so those elements could not be targeted from a `code` block. Containers already worked.
* Spectra V3: compiling a container border now raises an advisory — `spectra/container` declares no border support in Spectra Blocks 1.0.4, so the attributes are written but the block will not paint them.

= 2.1.2 =
* Gutenberg fix: a grid cell that was a container with children lost everything you styled on it — background, padding, radius, border, min-height, CSS class and alignment — because only its children were emitted into the column. Pricing-card style grids rendered as loose text on the page background.
* Gutenberg fix: `border.width` given per side (for example `[0,0,1,0]` for a bottom hairline) was ignored, so a one-sided rule drew a full box on all four sides. Per-side widths now follow normal CSS shorthand rules.
* Gutenberg fix: a CSS class on a `buttons` node never reached the button row, so it could not be targeted from a `code` block.

= 2.1.1 =
* Elementor fix: a section that set both `contentWidth: "alignfull"` and `innerWidth` ignored the width and rendered edge-to-edge. That pair is the documented full-bleed-background / boxed-content section, and it now produces one.
* Elementor fix: containers with no padding in your JSON inherited Elementor's 10px container default, nudging content off the section rail and compounding with nesting depth. The same JSON already sat flush in Spectra and Gutenberg; now Elementor matches.
* Elementor fix: `grid` nodes discarded every container field except gap and class — `margin`, `padding`, `background`, `radius`, `border`, `minHeight` and alignment were dropped silently. A grid is now compiled as the container it is.
* Elementor fix: internal wrapper containers (button rows, carousel, gallery, code) picked up the same 10px default, insetting buttons from their column.

= 2.1.0 =
* Product rename to **SectionMate MCP** (slug `sectionmate-mcp`, main file `sectionmate-mcp.php`).
* New SectionMate identity across the settings screen and the front-end Inspect / Annotations UI: slate ink, electric indigo and warm cream replace the previous purple/lilac theme. New logo, admin sidebar icon, and an inlined brand mark on the inspector panel and floating bar.
* WP-CLI: `wp sectionmate selftest` (the old `wp page-compiler selftest` still works).
* Fix: the self-test admin page was registered under one slug but linked under another, so it could not be opened. Both now resolve to `sectionmate-selftest`.
* Fix: a setting you switched **off** could be silently switched back on by the legacy-option migration, because an off value (`0` / empty) was mistaken for "not migrated yet". Affected **Remote animation libraries**. Migration now checks whether a setting exists rather than whether it is on, and runs once instead of on every page load.
* PHP classes `SectionMate_MCP_*`, constants `SECTIONMATE_MCP_*`, options `sectionmate_mcp_*`.
* Options migrate automatically from `ai_page_designer_mcp_*`, `ai_page_compiler_mcp_*` and `spectra_compiler_*`.
* REST namespace `sectionmate-mcp/v1`; legacy `ai-page-designer-mcp/v1` and `ai-page-compiler-mcp/v1` still accepted, so connector URLs already pasted into an AI client keep working.
* Post meta (`_apcm_*`, `_spectra_*`), the `sptk_` token prefix and admin form fields are unchanged for data continuity.

= 2.0.27 =
* Verified against Spectra Blocks **1.0.4** (live compile + render, Imagine V3 lab). 1.0.4 stops forcing a default button radius (theme/CSS decide when `radius` is unset) and fixes a PHP warning on custom SVG icons. Our V3 compiler already writes `style.border.radius` when you set `radius`, and does not rely on Spectra's old default. No compiler mapping change.

= 2.0.26 =
* `import_media` accepts attached / generated image bytes (`data:image/…;base64,…` or raw base64 + optional filename) as well as public https URLs. jpg/png/webp/gif only, 10 MB, max 8 per call, SVG rejected. Dedupes by URL or file hash.
* Compile preview reports `will_import_media` and does not sideload until apply.
* Skill / search_media copy no longer says uploading is unsupported.

= 2.0.25 =
* Tablet/mobile: compiler fills unset `sizeMobile`/`sizeTablet` (H1/H2 scale, body stays ≥16), `paddingMobile` (~half when desktop padding is large), `columnsMobile:1` on 3+ grids, and stacks wide rows (`directionMobile:column`). Header rows keep logo + CTA and hide mid-nav. Verbatim/decompile does not invent defaults.
* Gutenberg has no per-breakpoint layout — `apcm-stack-sm` / `apcm-header-sm` / `apcm-pad-sm` CSS at 781px. Desktop markup is unchanged.

= 2.0.24 =
* Gutenberg: a fixed `width` of 1000px+ (e.g. a 1400 rail) overflows the editor canvas when the list view is open and clips the left side if the parent is centered. Use `innerWidth` for a fluid rail; keep a left-aligned child *inside* it so type stays left. Advisor warns. Skill Part 1.7 updated.

= 2.0.23 =
* Gutenberg Cover: `justify:flex-start` writes native Content position = top (`contentPosition:"top center"` + `is-position-top-center`) so a header on a 100vh photo sits at the top instead of vertically centering the whole stack
* Gutenberg button: `border.color` adds `has-border-color` on the `<a>` (Watch the film Attempt Recovery)

= 2.0.22 =
* Gutenberg: a photo section with `innerWidth` stays `core/cover` (no `layout` on Cover — that is Attempt Recovery) and wraps children in a constrained Group (Inner blocks use content width)
* Gutenberg: `bgPosition` accepts `[x,y]` fractions or `{x,y}` as Cover `focalPoint`; `bgImageId` writes `wp-image-{id}`
* Gutenberg: button `class` lands on the `wp-block-button` wrapper (ghost / extra styles)

= 2.0.21 =
* Gutenberg: image `shadow` belongs on the `<img>` (core save()), not the `<figure>`. Figure box-shadow made the header mark invalid, so the editor showed the full-size photo.
* Gutenberg: a group `border.color` now adds `has-border-color` (same class the editor save() writes). Missing it was Attempt Recovery on the chip row.

= 2.0.20 =
* Gutenberg: core/button save() fidelity — emit every style the editor's save() writes on the `<a>` (including `text-transform:none`). Missing properties caused Attempt Recovery on the Zephyr CTA.

= 2.0.19 =
* Gutenberg: `innerWidth` on a column section writes Group Layout → Inner blocks use content width (`layout.constrained` + `contentSize`). No invented wrap when omitted
* Gutenberg: `bgGradient` → Group Background → Gradient (`style.color.gradient`)
* Gutenberg: `childrenWidth:"auto"` (and the row-auto default) writes child Width = Fit (`style.layout.selfStretch`). `fill` → Fill
* Gutenberg: a `width` on a **row** child is Fixed / flex-basis; on a **column** child it is CSS `width` (flex-basis would have been height)
* Gutenberg: honour `widthUnit` so an 8px chip is 8px, not 8%
* Gutenberg: `heading` `tag:"p"` compiles to `core/paragraph` (nav labels are not H2s)
* Gutenberg: `htmlTag` → `tagName`, `class` → `className`, `border` object, container `shadow`

= 2.0.18 =
* Legacy image: stop defaulting `align` to `center`. That emitted WP `aligncenter` (`margin-left/right:auto`) on the frontend only, which ate leftover space and broke `justify:space-between` in the header. Spectra's stored default is empty; set `align:"center"` only when you mean it.

= 2.0.17 =
* Legacy: a scalar `gap` (e.g. 22) now sets BOTH row-gap and column-gap. `(array)22` + pad used to write column-gap 0, so header/CTA rows had no horizontal spacing
* All backends: flex rows without `childrenWidth` and without child `width`s default to `childrenWidth:auto` (nav, logo+menu, buttons, chips). Splits still set child width
* Legacy: do not invent a 1200px inner wrap when `innerWidth` is omitted — that boxed the frontend while the editor stayed full-bleed. Set `innerWidth` (e.g. 1400) when you want a rail
* Skill: document the row-auto default — think “when is this a full-width column?”, not “remember to set auto”

= 2.0.16 =
* Elementor: `childrenWidth:"auto"` now maps to admin-visible Width / Flex Size (widget Width = Inline, container Width = auto, Size = None) so `justify:space-between` actually distributes leftover space
* Elementor: `fill` / `selfStretch:fill` → Advanced Size = Grow (hero body under a header)
* Elementor: button widgets default to Inline unless `align:"full"`
* Elementor decompiler round-trips `childrenWidth` and `fill`

= 2.0.15 =
* Spectra V3 + Gutenberg: image `radius` writes core/image style.border.radius and save() markup (has-custom-border + img border-radius) so the editor shows the same corners as the front end
* Spectra V3: `borderWidth` / `borderStyle` / `borderColor` on containers and headings (chips no longer need a code.css hack)
* Spectra V3: `transform` → typography.textTransform; container `overflow`; `fill` → selfStretch fill
* Gutenberg: same `transform` and border width/color/style on headings/groups

= 2.0.14 =
* Spectra V3: named `font` now actually applies on the front end (Spectra only emits a preset-slug CSS var, not style.typography.fontFamily)
* Dual-write responsiveControls.lg.fontFamily as the WP slug and define --wp--preset--font-family--{slug}
* Inject per-block [data-spectra-id] font-family CSS so already-saved V3 pages pick up the face without a full rewrite
* Gutenberg: collect named `font` families for the same Google Fonts enqueue (save() still emits the stored family string)

= 2.0.13 =
* Spectra V3: rgba/hsla text colours survive the front end (hex8 + textColor dual-write; WP 7 safecss strips color:rgba())
* Spectra V3: highlight span colours use the same safecss-safe encoding
* Spectra V3: named `font` families are collected, stored, and enqueued like Elementor (google_fonts in compile response)
* update_block: color/size/weight/align/font (and more) on spectra/content; color on V3 containers/buttons
* Overlay advisory skipped when every text descendant already has its own fill

= 2.0.12 =
* Gutenberg: emit useBlockProps.save() markup for cover, group, heading, paragraph, columns, buttons, image
* Gutenberg: heading color/size in HTML without invented has-custom-font-size (that class is button-only)
* Gutenberg: group wrappers now serialize padding/margin/color so they validate
* Gutenberg: Cover never writes layout.constrained; extra.layout stripped; overlay class order matches save()
* Gutenberg: vertical flex uses WP axis swap (justify→verticalAlignment) so values stay editor-valid
* Gutenberg: text align via style.typography.textAlign, not a fake textAlign attribute

= 2.0.11 =
* Spectra V3: full responsiveControls style mirror (radius, typography, color render correctly)
* Spectra V3: vertical flex maps to WP core attr swap so column justify:space-between works
* Spectra V3: childrenWidth:auto also sets width:fit-content (selfStretch fit alone is a no-op)
* Front-end Inspect: select Spectra V3 blocks via data-spectra-id (was Legacy-only uagb-block-*)
* Annotations: keep hyphens in V3 spectraId so notes match list_blocks
* Scope copy: page designer (not theme/site header builder) — settings Can/Can't, skill, readme
* Skill: V3 field survival table updated (apcm-highlight, real breakpoints, code hoist note)
* replace_block / batch_edit use the page backend (no silent Legacy write on V3 pages)
* code.html now emits into core/html; image CSS-string width matches core save()
* V3 bgOverlay → dimRatio + overlay color; widthMobile / directionMobile
* Quieter column-justify advisories; audit body_typical ignores display numerals
* Gutenberg: core/cover for photo heroes; valid button save() styles; minHeightUnit vh; hide-chrome CSS fallback
* Gutenberg: Cover save() fidelity (padding on wrapper, dimRatioToClass, no fake layout); isDark from overlay; heading colors in HTML

= 2.0.10 =
* Spectra V3: orientation-aware flex axes; container align no longer sets textAlign
* Spectra V3: minHeight/height via responsiveControls; image width/height/object-fit in markup
* Spectra V3: no object blockGap (PHP warning fix); real responsive type + paddingMobile/Tablet
* Spectra V3: wrap, childrenWidth→selfStretch fit, multi-icon items[], update_block gap/minHeight
* Gutenberg: image dimensions + object-fit on static-save <img> markup

= 2.0.9 =
* Setup: MCP connector on/off switch (blocks tools without uninstalling; token kept)
* Purple toggle UI for connector + remote CDN libraries; dimmed connector body when off

= 2.0.8 =
* Prompt tips: highlighted “best results” guidance — section-by-section, visual references, readable type
* How it works workflow note + extra copy-ready prompts for those practices

= 2.0.7 =
* Safer production posture (option B): not always-on MCP; prefer staging/drafts; deactivate when finished
* Admin warning, Can/Can't, safety model, and readme updated; content remains after disable
* Notes that CDN libs baked into page content keep working without the plugin

= 2.0.6 =
* Side menu label: AI Page Designer; icon fixed at 20px when not selected
* Settings hero uses logo-transparent-nospacing.png
* Admin: Supported blocks tab (Spectra Legacy, V3, Gutenberg, Elementor)
* How it works copy: AI-authored JSON; Typical AI workflow; Tools list parity; red Can't palette

= 2.0.5 =
* Settings moved to top-level admin menu with custom side-menu icon
* Topbar logo column: min-height/max-height 100% for the logo image

= 2.0.4 =
* Settings: background #cfd1ff; 4px box borders; top bar logo | text then status chips in one row
* How it works step numbers: white, larger, centered in circle

= 2.0.3 =
* Settings dashboard layout: wider shell, top bar + status chips, main/sidebar columns on large screens
* Tools grid 3 columns; prompts 2 columns; Can/Can't side-by-side on wide viewports

= 2.0.2 =
* Settings UI: light lilac solid background (banner brand), no gradients, larger transparent logo
* More spacing; clearer badges/buttons; redesigned Can & can't tab
* Build tips → Prompt tips with more copyable examples (replicate design, media library, etc.)

= 2.0.1 =
* Brand refresh: purple/navy theme from new logo system on admin settings and Inspect UI
* WordPress.org icons and banners generated from brand assets
* Logo on Page Designer MCP settings header

= 2.0.0 =
* Rename product to **AI Page Designer MCP** (slug `ai-page-designer-mcp`)
* Soft positioning: design and edit pages with AI agents via MCP; compile to Elementor, Gutenberg & Spectra
* Migrate options from `ai-page-compiler-mcp` / spectra-compiler; legacy REST namespaces still accepted
* Admin Tools menu: **Page Designer MCP**

= 1.99.2 =
* Elementor: accept paragraph/text node types (same clean JSON as Spectra/Gutenberg)
* compile_elementor: accept json as object or string (MCP clients)
* Verified on Spectra Blocks 1.0.4 + Elementor 4.2.2 + Legacy 2.20.1

= 1.99.1 =
* Schema/backend routing, audit clamp sizes, validate_page, V3 text updates, save_raw_content wp_slash

= 1.99.0 =
* Full default Gutenberg backend (core/*)

= 1.98.2 =
* Spectra V3 field parity (Claude feedback)


== Upgrade notices (historical) ==

= 2.0.18 =
Legacy images no longer default to aligncenter (frontend margin:auto broke header space-between). Recompile image-in-a-row layouts.

= 2.0.17 =
Legacy rows: gap numbers now apply horizontally, and content rows default to childrenWidth auto. Recompile header/nav/CTA rows to pick up spacing.

= 2.0.16 =
Elementor rows with childrenWidth:auto now shrink children via native Width/Size controls (no CSS). Recompile Elementor header/nav rows so space-between works.

= 2.0.15 =
Image and container radius/border now live in block attributes (editor + front end). Recompile pages that faked corners with CSS.

= 2.0.14 =
Spectra V3 custom fonts now apply on the front end (preset slug + CSS). Refresh the page; recompile only if a heading still uses the theme face.

= 2.0.13 =
Spectra V3: rgba text colours and Google Fonts now render on the front end. Recompile V3 pages that used rgba() type or a custom font.

= 2.0.12 =
Gutenberg save() fidelity: Cover/group/heading markup now matches core save(). Recompile Gutenberg pages (do not Attempt Recovery on old Cover HTML).

= 2.0.11 =
Spectra V3 parity: full style mirror, correct vertical flex, childrenWidth fit-content. Recompile V3 pages to pick up layout fixes.

= 2.0.10 =
Spectra V3 layout/image/responsive fixes from round-2 feedback; Gutenberg image dimensions in markup.

= 2.0.9 =
MCP connector on/off switch under Setup — turn off after sessions without uninstalling.

= 2.0.8 =
Prompt tips include best-results guidance for section-by-section builds and readable design.

= 2.0.7 =
Safer production guidance: short sessions, drafts preferred, deactivate when finished. Content remains after disable.

= 2.0.5 =
Settings is a top-level admin menu item with custom icon (no longer under Tools).

= 2.0.3 =
Wider multi-column settings dashboard layout for large screens.

= 2.0.2 =
Light settings UI, larger transparent logo, Prompt tips tab.

= 2.0.1 =
Brand purple/navy UI + logo on settings; .org icon/banner assets.

= 2.0.0 =
Product rename to AI Page Designer MCP. Update your MCP connector URL from Page Designer MCP settings if needed. Options and tokens migrate automatically.
