Recent Activities

This page shows what are we working on.

swissup / module-sold-together-import-export

11 hours ago success
  • head
    • Refactor export classes; improve stability; use PHP 8.1 features; update composer.json to require PHP 8.1 1ff219

breezefront / module-breeze-ai

18 hours ago success
  • 1.9.0
    • Version 1.9.0 4800bc

    • fix(content-builder): the menu path is Swissup > Breeze AI > Models (#223) (#229) 7ee063

    • fix(content-builder): the menu path is Swissup > Breeze AI > Models (#223) cdb784

    • docs(models): say on the Model field what a cheap row cannot do (#223) (#227) 5ca280

    • fix(content-builder): refuse a page with nothing on it (#223) (#226)

      * fix(content-builder): refuse a page with nothing on it (#223)

      * fix(content-builder): a band that paints its own background is not empty (#223)

      * fix(content-builder): name the menu the admin sees, lead with the measured advice (#223) 837ec9

    • fix(content-builder): name the menu the admin sees, lead with the measured advice (#223) bbdb05

    • fix(content-builder): a band that paints its own background is not empty (#223) a6639a

    • docs(models): say on the Model field what a cheap row cannot do (#223) 6fa688

    • fix(content-builder): return focus to the toggle on close (#206) (#224) ab040f

    • fix(content-builder): report a cell that came out holding two blocks (#215) (#221)

      * fix(content-builder): report a cell that came out holding two blocks (#215)

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

      * fix(content-builder): tabs wins over a column count when classifying slots (#215)

      ---------

      Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 142565

    • fix(content-builder): refuse a page with nothing on it (#223) 9152e6

    • fix(content-builder): tabs wins over a column count when classifying slots (#215) 75d1b9

    • fix(content-builder): a band with nothing in it is reported (#213) (#222)

      Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 465fd0

    • fix(content-builder): return focus to the toggle on close (#206) a49f22

    • fix(content-builder): a band with nothing in it is reported (#213)

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 4e9410

    • fix(content-builder): report a cell that came out holding two blocks (#215)

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> f7f49c

    • feat(content-builder): close the chat panel from its own header (#206) (#219) a2d775

    • fix(content-builder): the theme's own header and footer are not bands (#216) (#217)

      * fix(content-builder): theme chrome is not a band (#216)

      * fix(content-builder): the plan call's no-brief fallback dictated a hero (#216)

      * fix(content-builder): the last rule said do not drop it, with no exception (#216)

      * docs(content-builder): docblocks asserted a theory the measurement withdrew (#216) 9aab14

    • docs(content-builder): docblocks asserted a theory the measurement withdrew (#216) 4428b7

    • fix(content-builder): the last rule said do not drop it, with no exception (#216) 77faeb

    • fix(content-builder): the plan call's no-brief fallback dictated a hero (#216) 1eba29

    • feat(content-builder): close the chat panel from its own header (#206) 4241e6

    • fix(content-builder): theme chrome is not a band (#216) 629ae3

    • fix(content-builder): columns capped at what the catalog offers (#210) (#214)

      * fix(content-builder): columns capped at what the catalog offers (#210)

      IMAGE_RULES asked for a "columns" container with "columns" set to how many
      blocks the band shows. The Content Builder offers 2, 3, 4, 5, 2-left,
      2-25-75 and 2-right, so a six-tile strip produced columns="6", which
      coerceOption() replaced with the prop's default of 2 — and since props are
      normalised before children, resolveSlotCount() then sized the container at 2
      and normalizeChildren() dropped four of the six tiles. The prompt was asking
      for something the catalog forbids, and the repair made it worse than the
      nearest legal value would have.

      Three changes:

      - the read-across rule stops at five and sends a bigger or uneven band to
      "grid", which the prompt never named at all. Two new rules give the model
      the vocabulary it was missing: "grid" with the preset whose slot count
      matches (gallery is 6, bento 5, read off this installation's catalog), and
      "tabs" for a band under tab labels, with every label needing content of its
      own — claude-sonnet-5 writes three titles and fills one, leaving two blank
      panels (#209, the "Latest deals" band).
      - coerceOption() falls back to the nearest numeric option instead of the
      default when the value is a number and the set has numbers in it. A tie
      goes to the larger: an empty slot costs the page less than a block dropped
      for want of one. A set with no numeric option, or a value that is not a
      number, behaves as before.
      - the warning says "replaced with the nearest available" when that is what
      happened. Dmitry pasted the old sentence into #210, so the wording is
      merchant-facing.

      Measured on the stand against the #210 reference design, through
      breezeai:page:build, 10 runs before and 10 after on gpt-4o-mini: the
      "columns 6" warning pair does not reproduce on that model either way, and no
      children were dropped to slot overflow in any run. "grid" went from 0/10 to
      2/10, so the vocabulary reaches a small model but thinly; band and node
      counts are unchanged. The three claude-sonnet-5 baseline runs show the defect
      the grid rule is aimed at — six tiles as columns=5 with two stacked in the
      first slot, and a tabs container with two empty panels in 3 of 3 — but the
      after-measurement on that row could not be taken: its API key ran out of
      credit part-way through.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

      * fix(content-builder): say what the grid rules really do (#210)

      Review findings on the rules added by 5187cc7, all of them text:

      - "a block past the preset's last slot is dropped" is not what happens.
      DocumentAssembler::slots() clamps the slot number into the container's last
      cell, so the block stacks beside what is already there and neither BandAudit
      nor ComponentValidator says a word. The rule named a penalty that does not
      exist, which is worse than naming none.
      - "six or more blocks" had no legal answer above six: gallery is the largest
      preset there is. The rule now names the ceiling and sends a wider band to a
      second band.
      - the catalog's own Grid text, rendered into the same message from the Content
      Builder repo, offers "grid" for blocks of *unequal* size — so six equal tiles
      read as a "columns" there and the rule contradicted it without saying so. The
      tie-break is now explicit, since the catalog text is not ours to change.
      - the RULES entry still opened "two or more blocks side by side is a 'columns'
      container" and contradicted itself two clauses later. Only the tail had been
      updated.

      Also: coerceOption() no longer calls a respelling a replacement. "4.0" and " 4"
      miss the strict comparison and resolve to "4" at distance zero; reporting that
      as "replaced with the nearest available" reads as a defect to a merchant, and
      these sentences are quoted into issues verbatim.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

      ---------

      Co-authored-by: Claude Opus 5 <noreply@anthropic.com> e40b73

    • fix(content-builder): say what the grid rules really do (#210)

      Review findings on the rules added by 5187cc7, all of them text:

      - "a block past the preset's last slot is dropped" is not what happens.
      DocumentAssembler::slots() clamps the slot number into the container's last
      cell, so the block stacks beside what is already there and neither BandAudit
      nor ComponentValidator says a word. The rule named a penalty that does not
      exist, which is worse than naming none.
      - "six or more blocks" had no legal answer above six: gallery is the largest
      preset there is. The rule now names the ceiling and sends a wider band to a
      second band.
      - the catalog's own Grid text, rendered into the same message from the Content
      Builder repo, offers "grid" for blocks of *unequal* size — so six equal tiles
      read as a "columns" there and the rule contradicted it without saying so. The
      tie-break is now explicit, since the catalog text is not ours to change.
      - the RULES entry still opened "two or more blocks side by side is a 'columns'
      container" and contradicted itself two clauses later. Only the tail had been
      updated.

      Also: coerceOption() no longer calls a respelling a replacement. "4.0" and " 4"
      miss the strict comparison and resolve to "4" at distance zero; reporting that
      as "replaced with the nearest available" reads as a defect to a merchant, and
      these sentences are quoted into issues verbatim.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 3fd6f8

    • fix(content-builder): columns capped at what the catalog offers (#210)

      IMAGE_RULES asked for a "columns" container with "columns" set to how many
      blocks the band shows. The Content Builder offers 2, 3, 4, 5, 2-left,
      2-25-75 and 2-right, so a six-tile strip produced columns="6", which
      coerceOption() replaced with the prop's default of 2 — and since props are
      normalised before children, resolveSlotCount() then sized the container at 2
      and normalizeChildren() dropped four of the six tiles. The prompt was asking
      for something the catalog forbids, and the repair made it worse than the
      nearest legal value would have.

      Three changes:

      - the read-across rule stops at five and sends a bigger or uneven band to
      "grid", which the prompt never named at all. Two new rules give the model
      the vocabulary it was missing: "grid" with the preset whose slot count
      matches (gallery is 6, bento 5, read off this installation's catalog), and
      "tabs" for a band under tab labels, with every label needing content of its
      own — claude-sonnet-5 writes three titles and fills one, leaving two blank
      panels (#209, the "Latest deals" band).
      - coerceOption() falls back to the nearest numeric option instead of the
      default when the value is a number and the set has numbers in it. A tie
      goes to the larger: an empty slot costs the page less than a block dropped
      for want of one. A set with no numeric option, or a value that is not a
      number, behaves as before.
      - the warning says "replaced with the nearest available" when that is what
      happened. Dmitry pasted the old sentence into #210, so the wording is
      merchant-facing.

      Measured on the stand against the #210 reference design, through
      breezeai:page:build, 10 runs before and 10 after on gpt-4o-mini: the
      "columns 6" warning pair does not reproduce on that model either way, and no
      children were dropped to slot overflow in any run. "grid" went from 0/10 to
      2/10, so the vocabulary reaches a small model but thinly; band and node
      counts are unchanged. The three claude-sonnet-5 baseline runs show the defect
      the grid rule is aimed at — six tiles as columns=5 with two stacked in the
      first slot, and a tabs container with two empty panels in 3 of 3 — but the
      after-measurement on that row could not be taken: its API key ran out of
      credit part-way through.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 5187cc

    • feat(content-builder): a flat node list the provider can check (#208)

      The page document used to travel inside the response envelope as a JSON
      string the model wrote by hand. On this installation's default model row
      that failed 7 builds in 9 — every one of them mismatched brackets, none of
      them a truncation. The envelope was schema-constrained since #188; the page
      inside it was not.

      Both reasons a document could not be schematised go away when the tree is
      sent flat. Recursion becomes a "parent" id, so no node contains another; a
      free-form props object becomes a list of {name, value} pairs, so every key
      is a declared string. The component types and the prop names are enums built
      per request off the catalog, spent against a budget so a large component
      library cannot outgrow what a provider carries.

      DocumentAssembler folds the list back into {type, props, children} and is the
      only thing that knows the answer arrived flat: ImageExtractor,
      ComponentValidator and DraftWriter are untouched. It is also where the
      repairs no schema can express live — an unknown parent, a parent that is a
      leaf component, a cycle, a repeated id, a slot past the container's last —
      and each degrades to a page the merchant can edit rather than to a refusal.

      Measured on the stand against the reference design from #210, two rounds of
      nine builds on gpt-4o-mini: 18/18 readable documents against a baseline of
      2/9, and 17/18 pages with content in them. The one exception emitted five
      bands and left all five empty, which is band loss (#207, now #213) and not a
      parse failure. claude-sonnet-5 stays at 3/3 with the same page quality.

      Also item H of the same issue: the refusal no longer tells every merchant to
      raise Max Tokens. That advice was wrong in 7 local failures out of 7, and it
      cannot be right on this path at all — every provider runs
      StructuredResponse::assertComplete() before returning, so a reply the limit
      ended is refused earlier with the provider's own sentence. Length is no
      substitute either: a whole page's node list measures 2.3-4.4 KB, so no
      threshold separates a complete answer from a truncated one.

      The columns value 6 contradiction (#210) and the site-chrome rule are
      deliberately untouched; they are separate work on the same prompt.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6c788c

    • fix(content-builder): review round on the flat node list (#208)

      A node whose parent is a leaf component now becomes a band of its own, like
      every other bad-parent case. Handing it over was the one case that lost the
      node instead of moving it: ComponentValidator drops the children of a
      non-container, and its warning names the parent rather than the blocks that
      went missing.

      A four-number band is accepted alongside the two-number one, because
      ImageExtractor::readBand() reads a box and takes its top and bottom. Dropping
      it took the key with it, and the key's presence is what marks a node as a band.

      The enum values are spent against a budget. OpenAI strict mode caps them per
      schema, and past that cap a merchant with a large component library gets a
      provider 400 on every build with nothing local to read. Over budget the field
      stays a plain string, spending the smallest and most valuable list first.

      The output-limit sentence is gone rather than conditional. Review asked whether
      the StopReason::LENGTH branch was reachable: it is not. All three providers run
      StructuredResponse::assertComplete() before returning, so a reply the limit
      ended is refused in the catch above with the provider's own sentence. Length
      was no better a signal — measured on the new shape, a whole page from the
      default row is 2.3-4.4 KB, so the 8192 threshold carried over from the old
      string sat above a complete answer and separated nothing.

      Re-measured after these changes, nine builds on gpt-4o-mini: 9/9 readable and
      9/9 with content.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 809592

    • feat(content-builder): a flat node list the provider can check (#208)

      The page document used to travel inside the response envelope as a JSON
      string the model wrote by hand. On this installation's default model row
      that failed 7 builds in 9 — every one of them mismatched brackets, none of
      them a truncation. The envelope was schema-constrained since #188; the page
      inside it was not.

      Both reasons a document could not be schematised go away when the tree is
      sent flat. Recursion becomes a "parent" id, so no node contains another; a
      free-form props object becomes a list of {name, value} pairs, so every key
      is a declared string. The component types and the prop names are enums built
      per request off the catalog, so a type the Content Builder does not offer is
      now refused at the provider instead of repaired here.

      DocumentAssembler folds the list back into {type, props, children} and is the
      only thing that knows the answer arrived flat: ImageExtractor,
      ComponentValidator and DraftWriter are untouched. It is also where the
      repairs no schema can express live — an unknown parent, a cycle, a repeated
      id, a slot past the container's last — and each degrades to a page the
      merchant can edit rather than to a refusal.

      Measured on the stand against the reference design from #210, nine builds per
      arm on gpt-4o-mini: 9/9 readable documents against a baseline of 2/9, and 8/9
      pages with content in them (one run emitted five bands and left all five
      empty, which is the band loss #207 is about and not a parse failure).
      claude-sonnet-5 stays at 3/3 with the same 15-21 components per page it
      produced before.

      Also item H of the same issue: the refusal no longer tells every merchant to
      raise Max Tokens. That advice was wrong in 7 local failures out of 7 —
      stop_reason=stop, one or two kilobytes each — and is now shown only when the
      provider reported the output limit or the answer is long enough to make it
      plausible.

      The `columns` value 6 contradiction (#210) and the site-chrome rule are
      deliberately untouched; they are separate work on the same prompt.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 61e76d

    • feat(search): RetrieverInterface over Magento search, and the condensed product record it returns (#190)

      Phrase plus resolved conditions in, condensed product records out. The one
      implementation reads the same fulltext collection the storefront's search
      results page reads, so a merchant's search tuning, the price index, the
      visibility rules and any relevance plugin apply to what the AI ranks.

      ProductRecordInterface fixes what a model may see — id, sku, name, price,
      stock, and only the attributes the query's own intent named — and the cap is a
      property of the type rather than a rule each call site has to keep obeying.
      Embeddings stop at the interface: a vector retriever takes the same phrase and
      the same conditions, so it arrives as a di preference.

      Also settles the question #200 left open: a skill's di.xml key and its
      getCode() are one identity, checked in both pools, with cross-pool uniqueness
      held by a test over the real di.xml.

      An unreachable search engine is indistinguishable from an empty catalogue here,
      as it is everywhere else in Magento — both adapters 2.4 ships swallow the
      engine's exception and return an empty response.

      Follow-ups: #204 (a range filter poisons later searches in the same process),
      #205 (the vocabulary's attributes are not bindable by the search request).

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> c8cdb8

    • fix(search): close the three low review findings (#190)

      A filter key the catalogue has no attribute for used to kill the whole
      call: addAttributeToSelect() throws for a code EAV does not know, and it
      ran before bindable() would have dropped it, so one unusable condition
      failed with Magento's wording where every other one here is dropped and
      logged. The intent's attributes go on the select one at a time now, and a
      code the catalogue refuses is left off and named in a warning. Vocabulary
      cannot produce one, but retrieve() is @api and its docblock invites the
      shape.

      getPrice()'s null branch cannot happen through this retriever — the price
      index is joined INNER, so an unpriced product is gone before a record is
      built. The nullable type stays, the reasoning on it stops describing a
      case this implementation cannot produce, and the effect that is real gets
      a line in the docs: a call can return fewer records than the engine
      matched.

      Two behaviours had no test and stayed green when removed: the multiselect
      branch that renders "style_bags: Messenger, Laptop", and the fallback when
      getAttributeText() cannot reach its source model.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 0281de

    • fix(search): close the review findings on the retriever (#190)

      Six things from the review of #203.

      The headline claim that an unreachable search engine is raised rather than
      returned as an empty list was false. Both adapters Magento 2.4 ships catch
      the engine's exception inside SearchAdapter\Adapter::query(), log it as
      critical and return an empty response, and 2.4 has no third engine — so
      nothing downstream can tell an outage from no matches. The @throws, the
      docs and the test now say that, and the test that mocked load() into
      throwing is renamed to the failures that do reach us: an unknown search
      request, a database error behind the price index.

      bindable() checked a condition's field name but not its shape. A filter is
      declared either as a term or as a range and carries only that kind's
      placeholders, while addFieldToFilter picks its branch from the value, so
      `['price' => ['80']]` passed the name check and was then unset by Cleaner
      without a word — the same silent drop the method exists to prevent. It now
      reads field => type out of the request and refuses a mismatch by name.

      The cross-pool test asserted an intersection with an empty set and stayed
      green when pointed at a pool that does not exist. It now scans every
      etc/*/di.xml the module ships and marks itself incomplete while no query
      skill is registered, so the day the first one appears somewhere this file
      does not look, the suite says so instead of passing.

      The collection build order is load-bearing — addPriceData() resolves the
      website from the collection's own store id at call time — and nothing held
      it; moving it above setStoreId() left the suite green. The call sequence is
      recorded and asserted.

      toPrompt() is positional and escaped nothing, so a product named
      "Classic Bag | 0.01 | in stock" rendered a price and a stock flag that were
      not the record's. The two structural characters are substituted in the
      rendering only; getName() and toArray() still hand back the catalogue's text.

      The error log interpolated the engine's own message, which carries the
      shopper's query — the thing 49ce312 had just fixed one line below.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 787d7a

    • fix(search): keep the shopper's phrase out of the log message (#190)

      The dropped-condition warning interpolated the raw phrase, so a newline in
      it would end the line and start one of the writer's choosing. The phrase
      now travels in the PSR-3 context array instead of the message, with
      control characters collapsed and the value capped at 120 characters; the
      condition names get the same treatment, since one can be a model's answer
      rather than this module's text.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 49ce31

    • fix(search): wire the retriever to a search request that exists (#190)

      Three things a live run against the Luma catalogue found, none of them
      visible to a unit test:

      Every search collection factory Magento ships is a virtualType over the
      plain product one, so the original type-hint named a class that does not
      exist and DI handed the retriever an ordinary product collection. The
      factory is now typed as the real class, bound in di.xml, and a collection
      that cannot search is refused by name.

      quick_search_container answers an empty search term with an empty result
      by construction, so the documented "empty phrase with filters" call could
      never have worked. This layer retrieves by phrase and says so; filters
      narrow what the phrase found, and browsing a filter set with no phrase is
      layered navigation.

      A condition the search request has no filter for is dropped by Magento's
      request cleaner without a word, leaving an unfiltered result set that
      looks exactly like a filtered one. On a stock Luma that is every attribute
      the vocabulary offers — color, size and material are is_filterable but not
      is_filterable_in_search. The retriever reads the request's own filter list,
      drops what it cannot bind and logs what it dropped.

      Measured after the fix: "bag" 8 records, with category 4 five, with a
      35-50 price range three; limit 500 clamped to 30 records / 1704 chars.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1e192b

    • docs(search): describe the retriever and the condensed record (#190)

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 45c99f

    • fix(skill): make a pool's di key and its skill's code one identity (#190)

      A skill had two names. Every consumer uses the di.xml array key — McpServer
      publishes a tool per key, config and request params carry a key, get() takes
      one — while SkillInterface::getCode() was read nowhere in the module. Two
      names nothing compares are two names that can disagree, and #200's review
      found where it would surface: the same code can sit in both pools, so the
      first consumer to publish tools out of both would mint two tools called
      "search" and neither pool would notice.

      Both pools now check the key against getCode(). SkillPool had to be widened
      for it — the type and null handling QuerySkillPool already does, because
      getCode() cannot be called on a non-skill without a \TypeError no caller's
      handler catches.

      Cross-pool uniqueness is the part no pool can check: handing one pool the
      other would construct every entity skill just to read their names, into the
      construction cycle di.xml already breaks with a Proxy. For this package it is
      held by SkillCodeTest over the real di.xml; for a third party it is the rule
      both interfaces now state.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> b1a4dc

    • feat(search): RetrieverInterface over Magento catalog search (#190)

      Phrase plus resolved conditions in, condensed records out. The one
      implementation reads the same fulltext collection the storefront's search
      results page reads, so a merchant's search tuning, the price index, the
      visibility rules and any relevance plugin apply here too.

      Embeddings stop at the interface: a vector retriever needs a raw phrase
      and a set of conditions, which is what retrieve() takes, so it arrives as
      a di preference and nothing above it changes.

      An unreachable search engine is raised, not returned as an empty list —
      a skill that reports it as one tells the shopper the catalogue is empty.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> ed7db4

    • feat(search): condensed product record for the ranking step (#190)

      The shape of a product a model is allowed to see, fixed in the interface
      docblock rather than derived from whatever a retriever selects: id, sku,
      name, price, stock, and only the attributes the query's own intent named.
      Description, images, URLs and the rest of the attribute set stay out, so
      "the model never sees more than the agreed slice" is a property of the
      type instead of a rule each call site has to keep obeying.

      Rendering lives on the record for the reason Vocabulary renders itself —
      the shape and the text the model is given are one decision.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 480d1d

    • Merge pull request #199 from breezefront/feature/structured-buildpage-188

      chore(content-builder): move BuildPage and PlanPage to structured output (#188) 48d7ef

    • Merge remote-tracking branch 'origin/master' into feature/structured-buildpage-188 13ea07

    • fix(content-builder): keep the failed payload out of the error log (#188)

      The payload a failed build or plan carries is the merchant's own brief
      read back, and a brief can hold copy that is not published yet. The
      error log is routinely shipped off the server, so it is the wrong place
      for it to accumulate.

      Both skills now split the line: the error keeps provider, model, stop
      reason, reason, detail and the payload's size, and the payload itself
      goes to logger->debug(). That is the switch Magento already has —
      Magento\Developer\Model\Logger\Handler\Debug suppresses the debug log in
      production until dev/debug/debug_logging is turned on, and leaves it on
      in developer mode — so no new setting is introduced for this. The byte
      count stays on the error line, which is what tells support whether there
      is anything to go looking for.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> d759a2

    • style(content-builder): drop two comments that narrate their own diff (#188)

      Comment-only: the token stream of both files is identical once comments
      and whitespace are dropped (token_get_all, T_COMMENT / T_DOC_COMMENT /
      T_WHITESPACE removed).

      The `bands` clamp and the re-encode in conforming() each carried a
      paragraph explaining why I changed the line. That reasoning belongs in
      the commit that made the change, where it is reviewable, and both are
      already there. The comment above the unreadable-document message stays:
      it carries a fact the code does not — a document cut off inside the
      "components" string produces no truncated stop reason.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 06fec3

    • fix(content-builder): keep a mangled payload, log it the same way twice (#188)

      Two findings from the review of 8568ecc.

      json_encode() answers false on invalid UTF-8, which is exactly what a
      reply the model mangled contains — so the payload was dropped on the one
      failure it was kept for. JSON_INVALID_UTF8_SUBSTITUTE keeps it, with
      var_export() left behind for the depth and recursion cases. The
      framework's Serialize\Json is not usable here: it takes no flags, so it
      would escape every non-ASCII character in a logged page, and it throws,
      which inside a throw expression would replace the real
      StructuredResponseException with an InvalidArgumentException.

      The cap and the middle cut move to AbstractSkill. PlanPage had its own
      rule — 2000 bytes off the head — so a plan that broke at its end had the
      end cut away, and the same payload was capped sixteen times shorter than
      on a build.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 2f9a7f

    • feat(skills): a query-scoped skill contract, so a search skill can exist (#189) (#200)

      * feat(skills): a query-scoped skill contract, so a search skill can exist (#189)

      SkillInterface::execute() takes an `object $entity`. A product-discovery
      skill has none: it is handed a phrase and the storefront scope, and the
      entities are its result. #190 and #192 both stop here.

      Adds, without touching anything that exists:

      * Api\QuerySkillInterface — getCode/getLabel/execute(string $query,
      array $history, array $options). Scope travels in $options, as it
      already does through GenerateService, AiRequest and every entity
      skill; a typed context object would have to be assembled by each of
      the planned callers for no consumer that exists yet.
      * Api\Data\QueryResultInterface + Model\Data\QueryResult — answer text
      plus the entities it refers to. A query skill returns structure, not
      a string: BuildPage shows the cost of the alternative, its caller
      json_decodes the result and finds a malformed answer three frames
      from where it was produced (BulkConsumer.php:348). Cost and latency
      stay out — the usage ledger (#171) already records both per request.
      * Model\QuerySkillPool — a second pool, not a widened SkillPool.
      Every consumer of SkillPool assumes entity semantics: SkillSupport
      and BulkAction\ViewData call getSupportedEntityTypes(), McpServer
      gives each pooled skill a tool whose schema requires entityType and
      entityId. One pool of a common parent type does not stay typed; it
      becomes a parent type plus an instanceof in four places, and a query
      skill nobody filtered out is published as a tool no client can call.

      QuerySkillInterface deliberately does not share a parent interface with
      SkillInterface. Declaring getCode() and getLabel() twice keeps this
      change strictly additive, so no implementor — ours or a merchant's —
      can break on upgrade; extracting the parent later is itself backwards
      compatible, so the option costs nothing to defer.

      The five existing skills, SkillPool, BulkConsumer and the MCP tool list
      are untouched. Unit tests cover resolution of both kinds and both
      cross-kind mistakes.

      Checked on 2.4.7 as well as 2.4.8 (#197): ConstructorIntegrity,
      TypeDuplication and ArgumentSequence against a standalone
      magento/framework 103.0.7.0-patch10 and 103.0.8.0-patch1 — clean on
      both, with the #197 widening re-introduced as a positive control to
      prove the gate was live.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

      * fix(skills): check the query pool and the result where they are built (#189)

      Review of the contract itself turned up four places where it promised
      more than it enforced.

      QuerySkillPool refused a misregistered skill only through the return
      type of get(): a \TypeError, which the LocalizedException handlers
      around every pool call do not catch, and only on the path that calls
      get() — getAll() has no return type to protect it and handed the wrong
      object straight to a consumer. The members are now checked once at
      construction, with the di.xml item's code in the message. SkillPool is
      left exactly as it was; widening its behaviour is not part of #189.

      QueryResult validated nothing, so the "structure beats a string"
      argument stopped one level short of the resolver that reads it. An id
      decoded from a model's JSON arrives as "42" as readily as 42, and a
      resolver comparing a string against a loaded product finds nothing and
      reports nothing. entity_type and entity_id are now normalised, a row
      carrying neither is refused, and the optional fields are left untouched.

      QueryResultInterface::getItems() documented its shape in PHPStan
      generics, which Magento's TypeProcessor cannot read — isArrayType()
      matches `[]` and `ArrayOf` only, so the shape would be taken for a class
      name the day a webapi route names this interface. Now `array[]` for
      Magento with the shape kept on a @phpstan-return beside it.

      docs/DEVELOPMENT.md still said every capability implements SkillInterface
      and registers into SkillPool, which is the sentence #190 would read
      before putting a search skill in the wrong pool. Both kinds are
      described now.

      Unit suite 2208 tests. 2.4.7 and 2.4.8 validator runs repeated on the
      amended tree, positive control still fires.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

      * fix(skills): refuse non-scalar refs, drop null pool items (#189)

      ---------

      Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 9f570c

    • fix(skills): refuse non-scalar refs, drop null pool items (#189) 215043

    • fix(skills): check the query pool and the result where they are built (#189)

      Review of the contract itself turned up four places where it promised
      more than it enforced.

      QuerySkillPool refused a misregistered skill only through the return
      type of get(): a \TypeError, which the LocalizedException handlers
      around every pool call do not catch, and only on the path that calls
      get() — getAll() has no return type to protect it and handed the wrong
      object straight to a consumer. The members are now checked once at
      construction, with the di.xml item's code in the message. SkillPool is
      left exactly as it was; widening its behaviour is not part of #189.

      QueryResult validated nothing, so the "structure beats a string"
      argument stopped one level short of the resolver that reads it. An id
      decoded from a model's JSON arrives as "42" as readily as 42, and a
      resolver comparing a string against a loaded product finds nothing and
      reports nothing. entity_type and entity_id are now normalised, a row
      carrying neither is refused, and the optional fields are left untouched.

      QueryResultInterface::getItems() documented its shape in PHPStan
      generics, which Magento's TypeProcessor cannot read — isArrayType()
      matches `[]` and `ArrayOf` only, so the shape would be taken for a class
      name the day a webapi route names this interface. Now `array[]` for
      Magento with the shape kept on a @phpstan-return beside it.

      docs/DEVELOPMENT.md still said every capability implements SkillInterface
      and registers into SkillPool, which is the sentence #190 would read
      before putting a search skill in the wrong pool. Both kinds are
      described now.

      Unit suite 2208 tests. 2.4.7 and 2.4.8 validator runs repeated on the
      amended tree, positive control still fires.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1d185e

    • Merge pull request #202 from breezefront/fix/openai-error-classification-201

      fix(providers): OpenAI 401/429 were never classified, and a bulk 429 was never retriable (#201) 89ddd9

    • fix(providers): mark a provider outage retriable too, 529 included (#201)

      Follow-up to the rate-limit work in this branch, which left 5xx alone.

      A provider that was reachable and could not answer says nothing about the
      request either, so it wants the same handling a 429 now gets: keep the bulk
      operation for the admin's Retry button instead of recording it as dead work.
      Model\Exception\ProviderUnavailableException carries the same
      TemporaryStateExceptionInterface, and all three text providers and both image
      generators raise it.

      The statuses are a list - 500, 502, 503, 504, 529 - and not a `>= 500`. 501 is
      the 5xx that will still be unimplemented tomorrow, and retrying it is the loop
      the list exists to avoid. 529 is in it because Anthropic answers an overloaded
      model with a status no range check written for the other four would catch.

      The provider's own words travel with the new sentence rather than being
      replaced by it. A 5xx from a proxy standing in for the endpoint carries the
      only explanation there is, and two existing tests rely on exactly that: the
      Gemini and OpenAI-image redaction tests use a 5xx to prove the API key is
      masked on the way out. Both still pass untouched, which is the check that the
      detail - and the redaction - survived the reclassification.

      Verified by running it: the six new cases all fail against the previous commit,
      the full suite is green (2205 tests), and di:compile's validators report no
      findings over the module's 225 classes.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> affc3b

    • fix(providers): OpenAI 401/429 were never classified, and a bulk 429 was never retriable (#201)

      handleException() decided what had gone wrong by looking for the status
      number in the exception message. openai-php never writes it there, so both
      branches were dead and every failure reached the merchant as the SDK's own
      sentence - the reported one being "OpenAI error: Request rate limit has been
      exceeded." The status is read off the exception now, the way Claude and
      Gemini already read theirs.

      It cannot be read the same way, though: openai-php exceptions have no
      getResponse(), they carry a public $response, and only ErrorException adds
      getStatusCode(). statusOf() names each shape instead of probing for one.

      An exhausted balance is separated from a rate limit while we are here. OpenAI
      answers both with 429 and only the error code tells them apart, and the two
      want opposite handling now that one of them is retried.

      Which is the second half. README promised that a 429 in Bulk Actions was
      "automatically retried by Magento"; it was not retried at all, because every
      provider failure reached AbstractConsumer as a plain LocalizedException and
      was recorded as STATUS_TYPE_NOT_RETRIABLY_FAILED - so a store that tripped
      its per-minute limit mid-run lost every remaining product permanently. A rate
      limit now raises Model\Exception\RateLimitException, which carries Magento's
      TemporaryStateExceptionInterface, and the consumer honours that interface on
      its LocalizedException branch as it already did on the NoSuchEntityException
      one. All three text providers and both image generators raise it.

      Magento still does not re-run a retriable operation on its own, so the README
      says what actually happens instead: the operation is listed in the admin's
      bulk detail page and the Retry button re-queues it.

      The unit tests fed handleException() `new \Exception('Error 429 Too Many
      Requests')`, a message openai-php would never produce, which is how the dead
      branches stayed green. They now build the SDK's real exception types and go
      through chat() rather than reflecting into the private method.

      Verified by running it: the five new OpenAI cases all fail against master -
      including one that master mislabels a statusless timeout as a rate limit
      because its text contains "429" - and the full suite is green (2198 tests).
      setup:di:compile's own validators report no findings on the module, the #197
      check.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 51119b

    • feat(skills): a query-scoped skill contract, so a search skill can exist (#189)

      SkillInterface::execute() takes an `object $entity`. A product-discovery
      skill has none: it is handed a phrase and the storefront scope, and the
      entities are its result. #190 and #192 both stop here.

      Adds, without touching anything that exists:

      * Api\QuerySkillInterface — getCode/getLabel/execute(string $query,
      array $history, array $options). Scope travels in $options, as it
      already does through GenerateService, AiRequest and every entity
      skill; a typed context object would have to be assembled by each of
      the planned callers for no consumer that exists yet.
      * Api\Data\QueryResultInterface + Model\Data\QueryResult — answer text
      plus the entities it refers to. A query skill returns structure, not
      a string: BuildPage shows the cost of the alternative, its caller
      json_decodes the result and finds a malformed answer three frames
      from where it was produced (BulkConsumer.php:348). Cost and latency
      stay out — the usage ledger (#171) already records both per request.
      * Model\QuerySkillPool — a second pool, not a widened SkillPool.
      Every consumer of SkillPool assumes entity semantics: SkillSupport
      and BulkAction\ViewData call getSupportedEntityTypes(), McpServer
      gives each pooled skill a tool whose schema requires entityType and
      entityId. One pool of a common parent type does not stay typed; it
      becomes a parent type plus an instanceof in four places, and a query
      skill nobody filtered out is published as a tool no client can call.

      QuerySkillInterface deliberately does not share a parent interface with
      SkillInterface. Declaring getCode() and getLabel() twice keeps this
      change strictly additive, so no implementor — ours or a merchant's —
      can break on upgrade; extracting the parent later is itself backwards
      compatible, so the option costs nothing to defer.

      The five existing skills, SkillPool, BulkConsumer and the MCP tool list
      are untouched. Unit tests cover resolution of both kinds and both
      cross-kind mistakes.

      Checked on 2.4.7 as well as 2.4.8 (#197): ConstructorIntegrity,
      TypeDuplication and ArgumentSequence against a standalone
      magento/framework 103.0.7.0-patch10 and 103.0.8.0-patch1 — clean on
      both, with the #197 widening re-introduced as a positive control to
      prove the gate was live.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 013f50

    • fix(content-builder): keep the failed payload and name the output limit (#188)

      Review of #199 found five things the structured-output move left behind.

      The providers carry no logger, so a payload that is not put on the
      exception is gone: StructuredResponseException now holds the raw reply
      and BuildPage logs it, which is what master's logUnreadableReply() was
      written to keep. Null on a truncation or a refusal, where the stop
      reason settles it before a body exists.

      A document cut off inside the "components" string is the one shape of
      #143 the stop reason cannot catch — the envelope closes normally and
      the provider reports an ordinary stop — so the sentence for an
      unreadable document names the output limit itself.

      Also: a negative "bands" is read as no count rather than as the number
      it says, because `minimum` is outside the subset Schema\Validator
      accepts and BandAudit would otherwise report "read as -2 band(s)"; and
      a reply that missed the schema no longer borrows the document's
      sentence, in the same words and punctuation on both skills.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 8568ec

    • chore(content-builder): move BuildPage and PlanPage to structured output (#188)

      Both skills asked for JSON in the prompt and dug the answer out of the reply
      text afterwards. That cost us the one fact that separates the failures: a
      document the provider cut off at the output limit and a document the model
      mangled both reached the merchant as json_decode's "Syntax error" (#143), and
      they want opposite answers — raise a number, or try again.

      Both now call AiRequest::sendStructured() with a response schema (#187), and a
      call that produced no usable answer arrives as StructuredResponseException with
      a reason. Each skill turns the three reasons into three named sentences,
      carrying the provider's own message — the one that names "Max Tokens" — whole.

      PlanPage is fully structured: PlanSchema describes {"sections": [...]} and the
      skill parses nothing at all. extractJson() and its json_decode are gone.

      BuildPage gets as far as the schema subset reaches. A Content Builder node has
      free-form "props" and recursive "children", and neither is expressible under
      OpenAI strict mode — nor, therefore, under the intersection Schema\Validator
      holds. So DocumentSchema schematises the envelope ("bands" an integer or null,
      "unsupported" a list of strings, both checked on the way out) and the document
      travels inside it as a JSON string, read by the new ContentBuilder\DocumentReader.
      DocumentSchema's class comment sets out why, and what finishing the job would
      take: a flat node list with parent references, which is a measurement to run
      rather than a refactor to slip in here.

      What that removes from Model/Skill: the fenced-block regex, the widest-bracket-run
      candidates, the lone-backslash repair pass, the hand-written bracket scanner that
      sliced a broken list element by element, and the envelope scraper. Around 630
      lines of BuildPage. The partial-page salvage goes with them — a truncation is now
      reported as a truncation before any document exists, instead of handing back a
      prefix nobody could verify.

      StopReason stays and is unchanged; it is read off the response field by
      Provider\StructuredResponse, which is where it already was.

      Checked: no json_decode or preg_match over a model reply is left in Model/Skill;
      the two schemas pass Schema\Validator and lower onto all three dialects
      (SkillSchemaTest); truncation, refusal and a reply that missed the shape each
      produce a different named error in both skills' tests. 2185 unit tests pass.

      Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> e506b3

  • 1.8.22
    • Version 1.8.22 9893b7

    • fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197) (#198)

      * fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)

      Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

      * style(providers): wrap the cause-type comment (#197)

      Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

      ---------

      Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> 1d4842

    • style(providers): wrap the cause-type comment (#197)

      Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> 602747

    • fix(providers): StructuredResponseException cause type breaks di:compile on 2.4.7 (#197)

      Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> a961ae

swissup / hyva-social-login

19 hours ago success

breezefront / theme-frontend-breeze-enterprise-blank

23 hours ago success

breezefront / breeze-enterprise

23 hours ago success

breezefront / theme-frontend-breeze-enterprise-eir

23 hours ago success
  • 3.4.0
    • Product page: show price below the image on mobile; clamp short description 1b0bf5

    • Release 3.4.0 76642a

    • Minicart: hide quantity details on small screens 225339

    • Footer top content heading spacing reduced 66a864

    • Homepage: replaced logo images; replaced 2 Column components with single Grid 8584c9

    • Homepage: added separate mobile banner sliders in place of grids 25fb76

    • Homepage: use TikTok videos 18be0b

    • Restore compare link in account dropdown 7badee

    • Fixes for bundle products. 92c614

    • Homepage: removed gap between FAQ items on mobile 64766a

    • Replaced running line html with component 001c96

    • Add styles for slider dots component in grid layout 767bf3

    • Fix sticky title line-height variable in sticky cart 6af33d

    • Reduce spacing for headings level 1 9a63f6

    • Homepage: use new brand logos 96b432

    • Open cart sidebar after adding product to cart 30bffc

    • Fixed sticky cart and sticky columns conflict: options were not visible after scrolling 10cc88

    • Running line font-size reduced on home and product pages b362c5

    • Improved FAQ block on product page abd787

    • Moved Also Bought block after banner on product page; improved mobile styles c3edbb

    • Homepage: hide the top banner text on mobile d860bb

    • Homepage: changed banners grid text color 282fa5

    • Running line font-size reduced on homepage 48b0ec

    • Adjust running text font size and padding d95aec

    • Change header slideout menu layout from flex to grid 3ab3fe

    • Running line font-size reduced df52ab

    • Updated SoldTogether styles 81070c

    • Use Stripe layout as default for Frequently Bought Together block 49d70b

    • Added sticky Add to Cart bar on product pages cec5bb

    • Move stock status above the image on mobile 5f5712

    • Moved stock status above the add-to-cart form c4b645

    • Moved category CMS to bottom and added sample static block content 7b6b1c

    • Content Builder images updated f6cdbe

    • Add gap and margin adjustments for product item actions in listing layout 2363f7

    • Add layout configuration for catalog search results page 285928

    • Login customer form styles update. cbc7e9

    • Add layout styles for layered navigation and blocks; update variables 8b00ba

    • Improved out-of-stock and bundle products styles 56e102

    • Added Sold Together styles 4ad53f

    • Sticky media and info blocks on product page on desktop e6754a

    • Improved filter dropdown styles 61da32

    • Move ECI after description 4bf01c

    • Few minor changes to shop by dropdown 22fc09

    • Enhance header slideout menu. Adjust top offset. 75cdb2

    • Related product block. Fix issue incorrectly placed button when critical CSS enabled. 63d63b

    • Added testimonials and FAQ blocks to the product page e95221

    • Remove listing banners except for the first one 0b5f7c

    • Added ECI config, layout, and styles 9823dd

    • Updated styles and scripts to work with Qty Switcher module 59ad66

    • Minicart. Update slideout positioning and add scroll tracking for dropdown dialogs 96fbb9

    • Update header panel styles for pagebuilder slider. 4967ce

    • Refactor header panel: remove EasySlide integration, add header panel info block, and update styles for header panel slider 662724

    • Remove EasySlide configuration and related HTML content ea5c76

    • Add noEscape directive to continue shopping button URL ac4b9c

    • Update listing gap variable to use defined spacing 41f0ca

    • Add CSS class to main title in checkout cart layout dc41f6

    • Shopping cart page is done. c1d6d0

    • Shopping cart page in progress 8ca297

    • Shopping cart page 9fa558

    • Enhance quantity input styling and functionality. Apply border-radius to buttons and ensure step attribute is valid. 1bcebb

    • Add padding variable for navigation link caret. aa8d04

    • Quantity wrapper component. f57c8d

    • Product page. Adjustments for related products layout. aa27ee

    • Product page. Add new layout container. Place BCB marquee content there. a2fe52

    • Product page. Enhance gallery configuration and update layout variables for improved styling and responsiveness. b89117

    • Product page. Add grid area for price information and implement gallery layout for improved product display. 76b0b3

    • Product page. Add focus ring styling for checkboxes in related products block to enhance accessibility. ab2227

    • Product page. Refactor related products layout for improved responsiveness and styling consistency. 5aa789

    • Product page. Update quantity button icons to use mask images for better scalability and styling. 9576ee

    • Product page. Related products block. 7ed6de

    • Product page. Implement review popup for full review text display and enhance accessibility. d38885

    • Product page. Add toggle for "Write Your Own Review" form and adjust styles for review list 9973d8

    • Product page. Revies list is done. Revie form next. 5bf007

    • Theme Editor: added Listing, Animations, and Advanced sections a4451d

    • Theme Editor: added Colors section 390891

    • Product page. Reviews in progress... e13b99

    • Theme Editor: added Layout, Typography, Overlay Header, Border Radius, Color and Font Palettes ff5e62

    • Productpage. Review in progress... 7059e9

    • Productpage. Review in progress. 669824

    • BCB featured product component. 0651cf

    • Add styles for featured product details, SKU, and price row in BCB d65f2d

    • BCB accordion styles at product info main. 1a262b

    • Installer. Add accordion for products. 466a5b

    • Add padding to swatch options in product add form for improved layout f10f77

    • Product main info ... in progress bf5c3f

    • Enhance product quantity input with increment/decrement buttons and update styles for better usability 72192e

    • Update product page medium image dimensions to 590x590 012c72

    • Fix minicart styles: set empty counter opacity to 0 79fcd1

    • Increased h2 bottom margin dff6ea

    • Product page action buttons. d008f0

    • Updated footer content links and section titles for improved clarity and user experience 241039

    • BCB homepage: fixed wrong image name b1a4f4

    • Rating stars styles in product listing f17a0f

    • BCB homepage: updated full width banner and sections spacings 2076c7

    • Fixed sort order of product details inside grid listing 9ad360

    • BCB homepage: updated Videos and full width banner sections 072733

    • Product page title font-size. d0987c

    • Footer. Minor fix for ring reval animation. Restore missing divider in footer. (#3) 54cdc4

    • Footer ring reveal animation added (#3) 4a8be8

    • BCB homepage: added animated badges to top banners grid 197e37

    • BCB homepage: added rotation animation for Full Width Banner 1420f9

    • BCB homepage: top banner updated a53bd5

    • Update favicon color 5ea666

    • Update favicon 18d8dc

    • Slightly update footer background. 5fcbfc

    • Footer content update. Add contact block (#3) dea42e

    • Fixed missing minicart overlay on first open (#2) 54feaa

    • BCB homepage: updated Browse categories block dbfa89

    • Breadcrumbs 196469

    • Done with search (#2) d8e0f3

    • Enhance minisearch functionality with slideout behavior and styling adjustments without Ajaxsearch (#2) 8c4a9d

    • Minisearch styles when ajaxsearch disabled... in progress (#2) 259fac

    • Remove unused styles from viewcart action and update empty subtitle styles in minicart (#2) dba7a5

    • Minicart complete (#2) 56efd2

    • Search on desktop (#2) 9c587e

    • BCB homepage: added videos section 7ee864

    • Minicart inprogress (only buttons to adjust)... (#2) c1be91

swissup / theme-stigefabrikken-breeze

23 hours ago error
  • head
    • Checkout: Purchase Order Number made optional.

      Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> 6e5154

  • 1.0.86
    • Version 1.0.86 5da27f

    • Translation: Added checkout modification workaround 5872be

breezefront / breeze-blank

1 day ago success

swissup / gdpr

1 day ago success

breezefront / theme-frontend-breeze-enterprise-apollo

1 day ago success
  • 3.4.0
    • Version 3.4.0 53f50a

    • Product page: replaced running line and choose us banners with BCB components fab30a

  • 3.3.4
    • Version 3.3.4 289565

    • Fixed cart qty input style 5a9fba

    • Fixed wrong color id in theme editor settings 402ff7

    • Added width 100% for the contact form 4a1acf

    • Replaced PB footer top content with BCB version f1006d

    • Removed unused overlay header config bf9be6

    • Added Overlay Header settings to Theme Editor c859d3

    • Fixed layout shift when js is placed between price and swatches 7c4cc4

    • Move price styles to enterprise-blank 54a350