Recent Activities

This page shows what are we working on.

breezefront / theme-frontend-breeze-enterprise-eir

6 hours ago success
  • head
    • 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

    • Minicart update in progress.... c3d18b

    • Minicart update in progress... (#2) 55e7b9

    • INstaller. Create dummy CMS block header_panel_info to allow user add custom content (#2) f70d58

    • Adjust minicart counter (#2) 03e843

    • BCB homepage: Added brand logos section b905f1

    • BCB homepage: Added testimonials section 2ab2cf

    • Update newsletter styles: adjust input border and add consent positioning (#2) 02d37d

    • Header. Update search behaviou and look of the result dropown. 993432

    • BCB homepage: Added FAQ a0bfde

    • Add focused search styles. d80f88

    • BCB homepage: added new sections 875068

    • BCB homepage: added ECI and full-width banner d40cfa

    • Top navigation added. 8fa9a3

    • Remove color from menu links dec081

    • Clone currency and store switchers in header slideout menu for improved functionality de36e3

    • Enhance header and newsletter styles for improved layout and responsiveness in header slideout menu. b63dd0

    • Installer. Update header slideout menu styles. 825795

    • Update footer top content HTML to enhance newsletter section styling.
      Don't set font familiy for header in Page Builder ba0b81

    • BCB homepage WIP b228af

    • Basic navpro slideout styles 75ac0e

    • Refactor header slideout menu and newsletter styles for improved layout and consistency 3b42ff

    • Header slideout menu... d4f174

    • Listing styles for the list mode and old price update. cbff61

    • Update README.md to clarify usage of `with-bottom-divider` CSS class and provide detailed instructions 1b9ea5

    • Pagination and make with-bottom-divider general. 37ea17

    • Added list banner 5 config; updated installer, moved it to json a43985

    • Add new layout configuration and remove easybanner references; update footer content and styles 87165b

    • Refactor easybanner configuration and installer for product listing banners 8b1d0d

    • Installer. Listing grid banner added. 914c01

    • Add swatches variables and layout styles d59581

    • Update positioning for filter title and content in layered navigation 8cbd21

    • Add image dimensions for product listings and related widgets in view.xml 6985cd

    • layout and styles for category view, product toolbar, shop by button 08fd27

    • Add layered navigation styles and update imports 944ff1

    • Keep working on prodcut listing and category page. 411088

    • Keep working on product listing toolbar. ebcfac

    • Working on product listing and toolbar look. 1ab39d

    • Add Cooper* font 132906

    • Add listing item styles and variables e725b5

    • Product lisnting in progress - grey bg and border radius. 4e7c35

    • Add new variable files for colors, typography, header, footer, icons, navigation, popover, minisearch, and gradient ba122c

    • Refactor footer and newsletter styles for improved consistency and responsiveness d831e4

    • Footer top newsletter - some updates. 452ca5

    • Add footer top content block and styles for newsletter integration (WIP) 128131

    • Footer bottom. 8d58ea

    • Add footer content block and styles for enhanced footer layout 4d6b1b

    • Header. Hide menu on medium screens. Update search look on monile. e0146d

    • Installer. Add navigation slideout menu a89fd5

    • Add EasySlide functionality for header panel with custom slides and styles e55b0b

swissup / theme-frontend-argentohyva-chic

10 hours ago success

swissup / hyva-ajaxsearch

12 hours ago success

breezefront / module-breeze

14 hours ago success
  • head
    • Gallery: Restore proper tabindex in destructor 19bb81

    • Gallery: Use slider fallback for mobile devices for `expanded` option 7847d1

    • Improved slider destructor to cleanup listeners and markup dd8400

    • Remove unused code 6292c3

    • Do not close accordion when multipleCollapsible is used b1b4a7

    • LazyAsync 9e0a37

    • Removed redundant 'contentUpdated' dead1b

    • Faster DOM traversal after contentUpdated event 9c2d61

    • Simplify quotedScope from prev commit 37a7b7

    • Improve scope binding match logic
      minicart_content should not match x_minicart_content;
      minicart.content should not match minicart_content 5e9bca

    • Speculation rules: fixed not working exclude rules c78263

    • Added missing destructor to pagebuilderCarousel 81d228

    • Do not close dropdownDialog when dragging the slider inside eed793

    • Do not include requirejs-config if it's empty b4fbfe

    • Defer requirejs-config as all other scripts are deferred too 861179

    • Preload image from category-view block if main image is not found de4258

  • 2.31.4
    • Version 2.31.4 53d648

    • Improve cache key for js block f390bf

    • Update third-party JS libraries

      - underscore 1.13.6 -> 1.13.8
      - knockout 3.5.1 -> 3.5.3
      - js.cookie 3.0.1 -> 3.0.8
      - dayjs 1.11.7 -> 1.11.21
      - panzoom 4.5.1 -> 4.6.2
      - swiped-events 1.1.7 -> 1.2.0
      - tabbable 6.1.1 -> 6.5.0
      - focus-trap 7.3.1 -> 8.2.2
      - scrollyfills 1.0.3 -> 1.0.4
      - lite-yt-embed: 0.3.4 1d4f5d

    • Use view transition when prefers-reduced-motion is not set to `reduce` c4ed65

    • Remove useless comment 80489b

    • Fixed empty pages in Instagram Browser when view transition is enabled
      Closes https://github.com/breezefront/theme-frontend-breeze-evolution/issues/23 1c3432

    • fix(scroll-reveal): guard optional BreezeThemeEditor lookup with module-enabled check

      class_exists(BreezeThemeEditor::class) only reflects composer autoload
      availability, not whether Swissup_BreezeThemeEditor is actually
      enabled. When it's disabled, di.xml preferences aren't loaded, so
      ObjectManager::get(BreezeThemeEditor::class) fatals trying to
      instantiate the bare ValueRepositoryInterface it depends on
      (fixes #100).

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

swissup / argento-hyva

15 hours ago success

swissup / module-argento-hyva

15 hours ago success

breezefront / theme-frontend-breeze-enterprise-apollo

16 hours ago success
  • head
    • Sync gallery slider arrows with theme settings 6ce6f4

    • USP block content updated 96047c

    • Updated footer_top_content block content 946d00

    • Updated BCB homepage: moved to json; updated benefits block content 62bfe1

    • Fixed listing styles on Magento < 2.4.8
      LESS ^3.2.0 bugfix: This css block moved to the start of the styles. a38c1c

    • Firefox: Fixed horizontal scrollbar inside search slideout 613e59

  • 3.2.0
    • Version 3.2.0 4c4818

    • Per page limits according to design e54d9e

    • Fixed empty search slideout in Safari browser after closing keyboard cb1f77

    • Show testimonials block under category decsription, see #14 b30944

    • Theme Editor: Allow using gradients as bg color 3cb9ff

    • Improve recent products widget styles in sidebar 3d94fb

    • Revert "Move testimonials below category description"

      This reverts commit 046f5bb650b031b250b05a0009b5ec2edc27a619. 66ab0d

    • Move testimonials below category description 046f5b

    • Use `apollo-` prefix for custom quote icon 7f3938

    • Prevent zoom om mobile devices when focusing quantity field 0de256

    • Limit upsell and related products to 6 items on the product page 94dd46

    • Added BLB installation: Testimonials widget on category pages 60339b

    • Testimonials and BCB slider styles bb8fa5

    • Fixed qty overlap with actions in minicart on small screens, see #16 96cd74

    • Enabled mobile slider layout for columns and grid 77cb58

    • Created separate mobile slider 8880b4

    • Sync products widget styles with product list. Closes #13 241874

swissup / module-ajaxpro

16 hours ago success
  • 1.7.39
    • Version 1.7.39 650093

    • fix: guard productInfoResolver call in ajaxSubmit (#57 follow-up)

      Found while verifying the YQE-14065 fix on the customer's dev store:
      their theme (Webspaceconfig/Webspace) ships its own outdated copy of
      Magento_Catalog/js/catalog-add-to-cart.js that predates Magento core
      adding productInfoResolver support. Our widget extends that file as
      its base ($.mage.catalogAddToCart) and called
      self.options.productInfoResolver(form) unconditionally, so on that
      theme every ajax add-to-cart submit threw
      "productInfoResolver is not a function" before the request was even
      sent — configurable/size options couldn't be added to cart from
      category listing pages.

      Root cause is the theme's stale file, not this module — reported
      separately. This guard just keeps ajaxpro working regardless of
      whether the base widget a theme provides happens to support this
      option.

      Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 320f71

  • 1.7.38
    • Version 1.7.38 3b2284

    • Merge pull request #58 from swissup/fix/gift-card-options-data-post-57

      fix: stop stripping data-post so third-party widgets can still read it (#57) eb4490

    • fix: pass bind:false when reusing ajaxcian-data-post as an _ajax() helper (#57)

      Copilot review on #58: _confirm()'s replacement click handler runs on
      every confirmed compare remove/clear click and constructs
      AjaxproAjaxcianDataPost({}, element) to reuse its _ajax() method. With
      the default options ({} means options.bind stays true), _create()
      also calls _bind(), adding a brand new click listener on the same
      element — and since data-post is no longer stripped (YQE-14065), that
      _bind() call succeeds every time instead of bailing out on a missing
      attribute like it used to.

      None of those listeners are ever removed, so click N fires N ajax
      requests: click 1 adds listener #1 (plus its own direct call), click 2
      runs the _confirm handler directly again AND listener #1, click 3 runs
      the _confirm handler, listener #1, and listener #2, etc.

      Fixed by passing { bind: false } — this call only ever wanted _ajax()
      as a helper method. Verified by reverting locally and re-running the
      new test: 2nd click produces 3 ajax calls instead of 2 without the fix.

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

    • fix: stop propagation in compare.js rebind, close double-submit (#57)

      Copilot review on #58: Swissup_Ajaxpro/js/compare's _confirm() rebinds
      the click handler on compare remove/clear-all links on every
      compare-products section update:

      $(selector).off('click').on('click', function (e) { ... });

      .off('click') strips ANY click handler already on the element,
      including ajaxcian-data-post's own (which stopPropagation()s). The
      replacement handler here called _ajax() directly without stopping
      propagation of its own. Since ajaxcian-data-post no longer strips
      data-post, a click after this rebind reached both this handler's ajax
      call and the native document-delegated mage.dataPost handler — a real
      double submit for compare remove/clear links specifically. Previously
      this was masked because data-post was already gone by the time any
      rebind happened.

      Added e.stopPropagation() to this handler too, plus a regression test
      that reproduces the exact rebind sequence and asserts exactly one ajax
      call / zero native-handler calls (and fails without the fix, verified
      locally). Needed two additions to the jest $.widget shim along the
      way: this._super() support (compare.js's _create calls it) and calling
      the widget constructor without `new` (compare.js does
      AjaxproAjaxcianDataPost({}, element) directly, as real jQuery UI
      widget constructors support).

      Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 727bd4

    • fix: bind click handler synchronously, drop startup delay (#57)

      Copilot review on #58: the widget still delayed binding its own click
      handler by 500ms. That delay used to be harmless because the old
      buggy code removed data-post synchronously in _bind(), so the native
      mage.dataPost document-delegated handler couldn't match during the
      window either. Now that data-post stays on the element permanently,
      a click within that window reached mage.dataPost alone and triggered
      a full page navigation instead of Ajaxpro's ajax request.

      No commit in this file's history documents a reason for the delay.
      Binding synchronously closes the window; our handler is on the
      element itself so it still runs (and stopPropagation()s) before the
      event can reach the document-level native handler regardless of when
      it was bound, as long as it's bound before the click happens.

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

    • fix: clone cached data-post before mutating it (#57)

      Copilot review on #58: element.data('post') returns jQuery's cached
      object for the attribute (same reference on every call). _ajax() was
      mutating that object's .data (adding form_key) in place, so a later
      $(el).data('post') read — by this widget on a second click, or by a
      third party like Mageplaza_GiftCard/js/view/information.js — would see
      the leaked form_key instead of the original payload.

      Also documents the stopPropagation() trade-off raised in the same
      review: it can't be scoped to only the native mage.dataPost handler
      (jQuery event namespaces don't affect propagation, and mage/dataPost.js
      is a core file we don't patch) — no other ancestor-delegated click
      listener touches these links today, so accepted as-is.

      Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 75a2b4

    • fix: stop stripping data-post so third-party widgets can still read it (#57)

      ajaxcian-data-post.js renamed data-post to data-post-ajax and removed the
      original attribute when hijacking a link for ajax submit. Any other script
      reading $(el).data('post') from the same link after that point (e.g.
      Mageplaza_GiftCard/js/view/information.js, on the same .tocompare /
      .towishlist links used to build Gift Card add-to-wishlist/compare payloads)
      got undefined and threw, aborting its own init before it could render the
      Gift Card options form.

      Keep data-post intact and use stopPropagation() instead to stop the native
      mage.dataPost document-delegated click handler from double-submitting.

      Verified live on the customer's dev store: Gift Card options render
      correctly with the patch, no duplicate ajax request on click.

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

breezefront / module-breeze-content-builder

16 hours ago success
  • head
  • 1.1.0
    • Version 1.1.0 d0ef07

    • Merge pull request #35 from breezefront/feat/ai-components-bridge

      feat(content-builder): apply AI-generated components to the working copy (#34) fa5587

    • Added new Video component 94bb8d

    • fix(content-builder): clone the incoming array before installing it as the working copy d02528

    • fix(content-builder): stay a no-op until sub-modules finish initializing 2c2289

    • fix(content-builder): commit a pending dirty edit before pushing the AI snapshot 3a1ce7

    • feat(content-builder): apply AI-generated components to the working copy (#34) 030d4c

    • Added 5 columns layout for the Columns component 4adc04

breezefront / module-breeze-ai

19 hours ago success
  • 1.3.0
    • Version 1.3.0 050f99

    • Merge pull request #83 from breezefront/fix/ai-buttons-overlap-name-82

      fix(admin): stop AI buttons overlapping long field values (#82) 3348b6

    • fix(admin): lay AI actions beside field, not over it (#82) 3eb55f

    • fix(admin): stop AI buttons overlapping long field values (#82) 1a4854

    • Merge pull request #81 from breezefront/feat/editor-chat-panel

      feat(content-builder): chat panel in the editor for building and refining a page (#64) 0932fb

    • fix(content-builder): ground every request in the live working copy

      pageContext() only supplies the page's title and id, so a first chat
      message had no representation of the page's actual content, and manual
      edits made between AI replies were silently overwritten by the last
      frozen AI document. Every request now seeds or refreshes its document
      turn from contentBuilder.getComponents() instead. Also drops historical
      image attachments from the request — maxBytes is sized for one
      screenshot under post_max_size, and replaying every prior attachment on
      each follow-up can push the aggregate body past that limit. 9ca7e3

    • fix(content-builder): give the message textarea an accessible name b48efb

    • refactor(content-builder): share the chat loop between the modal and the editor panel (#64)

      Extracts turn storage, chat-bubble rendering, and copy-to-clipboard into
      chat-core.js, used by both chat.js's field-targeted modal and
      bcb-chat-panel.js's docked panel. The request/response cycle stays local
      to each caller — they talk to different skills with different payload
      and response shapes, and forcing that through one function would trade
      duplication for a worse, branchier abstraction.

      Fixes two bugs the duplication had introduced in bcb-chat-panel.js: the
      Copy button was never bound (only chat.js's copy of the handler existed)
      and a stale "type a message" error could survive a valid follow-up
      submission. 1ab342

    • fix(content-builder): drop a stale AI response after a page switch (#64) 371c91

    • fix(content-builder): keep the generated document in follow-up history, guard double-submit a48469

    • feat(content-builder): chat panel in the editor for building and refining a page (#64)

      Docks a conversational panel into the Content Builder editor's tree panel
      via a mixin on bcb-panel.js. Every reply is applied straight into the
      working copy through content-builder.js's applyAiComponents(), and every
      request is sent persist:false so nothing reaches the draft until the
      admin uses the editor's own Save/Publish. 9e66ae

    • feat(content-builder): add non-persisting build endpoint and editor chat config (#64)

      Adds options.persist=false to Controller/Adminhtml/Page/Build so a request
      can return generated components without writing the Content Builder draft.
      Extracts vision-model selection out of BuildFromScreenshot into a shared
      VisionModelProvider, and adds Block/Adminhtml/Editor/AiChatConfig plus its
      layout/template to seed the editor route with the same config. 6eca5e

    • Merge pull request #80 from breezefront/claude/focused-payne-b60751

      fix(content-builder): reproduce every band the screenshot shows (#74) a84559

    • fix(content-builder): word the band warnings for the request that was sent (#74)

      Four defects found reviewing the review fixes.

      The count-mismatch warnings hardcoded "The reference design was read as ...",
      but the mismatch branch runs whether or not a screenshot was attached — the
      prompt asks for a band count on every build, not only on the image path. A
      text-only brief that came back over- or under-segmented therefore told the
      merchant about a reference design they never sent, on the build note and in
      the Content Builder editor. The flag that decides whether to warn now decides
      what the warning calls the thing the page was counted against.

      The band-wrapper warnings borrowed ComponentValidator's "root[N]" label while
      numbering a different list. The validator indexes the raw model output, where
      a dropped node still consumes its index; the audit sees the list after
      validation. One dropped band was enough to put two warnings saying "root[0]"
      in one list, pointing at two different bands. They are numbered as bands now.

      readScreenshot() read and base64-encoded the file before ImageValidator got to
      apply MAX_IMAGE_BYTES, so an oversized design export cost about 2.3x its size
      in memory to reach a verdict available from filesize(). The admin path is
      unaffected: the browser posts base64 and no file is read.

      FiveBandMock::drawProductRow() took $bottom and never used it, so that band
      alone would not follow a change to the band split in draw() — in the one file
      whose docblock claims the band boundaries are readable in source. 685502

    • fix(content-builder): keep the band count when the reply carries prose (#74)

      Three defects found reviewing the band-audit change.

      The prompt says "no prose" and models add it anyway, which is why
      jsonCandidates() exists at all. It tries the widest "[...]" run before
      "{...}", correct while a bare list was the asked-for shape. With an envelope
      whose "unsupported" is omitted and "components" last, that run is exactly the
      components array: it parses, wins, and the band count is thrown away. The
      audit then reported "the AI did not report how many bands it saw" for a reply
      that did report it. A list is now held as a fallback and the remaining
      candidates are still tried for an envelope, so a reply that really is a bare
      list still resolves to the first list.

      spreadStackedSlots() decided a band was stacked from the count of the first
      slot alone. "children" written as a flat list of nodes rather than a list of
      slots lands there with the node itself as the first slot, and its own keys are
      counted as sibling blocks — {"type": ..., "props": ...} counts 2 in a
      two-column band and was shredded across the slots, replacing the accurate
      short-slot warning with a repair that never happened. Every entry now has to
      look like a node.

      render.php ignored the file_put_contents result and printed "(0 bytes)" with
      status 0 on an unwritable path, sending whoever reproduces a measurement to
      look at the build command instead. ee671e

    • fix(content-builder): reproduce every band the screenshot shows (#74)

      Shown a five-band reference design, gpt-4o-mini returned a document whose
      columns bands were fakes: the container had the right type and the right
      column count, but every card sat in its first slot, so the band rendered as
      one column with the rest empty. Measured over ten runs of the committed mock,
      that happened in nine of them.

      Three changes, in order of how much they hold:

      - ComponentValidator spreads a container whose whole band was written into
      its first slot, when the arithmetic leaves no other reading. Prompting
      against this only half worked; the repair is unambiguous.
      - The prompt asks for {"bands", "unsupported", "components"} instead of a
      bare array, spells out the slot shape, and names the columns-versus-
      product_grid choice. A bare array is still accepted.
      - BandAudit compares the band count the model reports to what it emitted and
      reports the difference, plus any band the catalog cannot express. It
      reports and does not retry: a second round trip would double the cost of
      every build to re-roll an answer a small model is as likely to get wrong
      again, and the missing band cannot be synthesised locally.

      Measured with the mock and expected shape now under Test/Fixture, driven
      through the new --screenshot option. Ten runs each, gpt-4o-mini:

      before: 5 bands x5, 6 bands x4, 1 hard failure; 9/9 mis-slotted
      after: 5 bands x10, 0 mis-slotted, 8/10 with both columns containers

      gpt-4o gets the slots right with either prompt, so the mis-slotting was
      model capacity and the prompt closes the gap on the small model. 0e5f35

    • Merge pull request #79 from breezefront/fix/build-note-page-id-type

      fix(content-builder): match cms_page.page_id column type in the build note table (#72) 7faf89

    • fix(content-builder): match cms_page.page_id column type in the build note table (#72) 7f6aaa

    • Merge pull request #78 from breezefront/claude/priceless-turing-a344ac

      fix(config): offer model names the providers still serve (#73) 21eee6

    • Merge pull request #77 from breezefront/claude/jovial-chatelet-3447d7

      feat(content-builder): report what the validator repaired (#72) d23f45

    • fix(content-builder): drop a corrupt build note instead of reporting a clean build (#72) e3994a

    • feat(content-builder): report what the validator repaired (#72) 4b47e4

    • fix(gemini): redact api key from error, notice for retired models (#73) 922d51

    • fix(config): offer model names the providers still serve (#73) b1963c

    • Merge pull request #76 from breezefront/claude/lucid-kepler-a7ce73

      fix(content-builder): keep authoring placeholders out of generated copy (#71) 6a5b87

    • fix(content-builder): keep required sample copy on the malformed-value path (#71)

      fallbackValue() blanked any authoring placeholder, so a required copy prop the
      model sent as an array — quote.text, in the fixture — came out empty, which is
      the broken component the omitted-prop path deliberately avoids. The rule now
      lives in one predicate used by both paths.

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

    • fix(content-builder): keep authoring placeholders out of generated copy (#71)

      The catalog's "default" attribute does two jobs: the value a renderer needs
      when a prop is unset, and the sample text the editor drops into a fresh
      component. The validator filled both, so "Discover Something New" shipped as
      the heading of three FAQ answers. Copy-typed props whose default reads as
      prose are now stored blank — blank, not absent, because the templates read
      `$props['heading'] ?? '<sample>'` — and the prompt marks them VISIBLE-TEXT
      instead of quoting the sample text that taught the model to echo it.

      A list-valued prop is blanked as an empty list, so an unusable faq_items no
      longer falls back to the two sample questions. Tabs keeps its declared titles:
      the container counts its slots from them.

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

    • Merge pull request #75 from breezefront/claude/amazing-lichterman-a86181

      fix(content-builder): keep the list props the model wrote (#70) 819b38

    • fix(content-builder): keep every tab title paired with its slot (#70)

      Dropping an unusable title compacted the list while normalizeChildren()
      truncates slots from the end, so ["Shipping", "", "Returns"] over three
      populated slots rendered "Returns" above the middle slot's content and threw
      the last one away.

      A list of strings is positional — its container pairs slot N with entry N —
      so an unusable entry is now replaced where it stands, taking the declared
      default's title for that position ("Tab 2"), which is what the container's
      renderer pads a short title list with anyway. Entries that carry their own
      content, like the FAQ items, have no slot to stay paired with and are still
      dropped.

      An empty list now says so rather than reporting that none of its zero
      entries was usable. efb774

    • fix(content-builder): read a keyed object as one list entry (#70)

      decodeList() accepted any array, so a keyed object counted as a list: an FAQ
      item the model sent unwrapped — {"q": ..., "a": ...} instead of [{...}] —
      was iterated field by field, turning the question and the answer into two
      bogus items, each with the other's text as its question.

      Only a real list is a list now. A keyed object carrying at least one declared
      field is wrapped as the single entry it is, so the question the model wrote
      survives; one carrying none falls back to the default with a warning naming
      the fields that were expected. 71d179

    • fix(content-builder): keep the list props the model wrote (#70)

      The catalog asks the model for the Tabs titles and the FAQ items as JSON
      arrays, then ComponentValidator coerced every non-scalar prop back to its
      default — so a generated FAQ arrived with its questions replaced by "Tab 1"
      / "Tab 2" / "Tab 3", and the Tabs slot count fell back to the default three,
      dropping any further answer with it.

      A prop whose declared default is a JSON array is now validated as a list
      instead of discarded: entries are cleaned one by one and written back in the
      JSON-string shape the editor stores and the renderers decode, so the slot
      count follows the titles the model actually wrote. The default is only
      reached when nothing usable is left, and the warning then names the reason.

      Recognised by the shape of the declared default rather than by prop name, so
      the FAQ accordion's question/answer items are covered by the same path. a4765b

    • Merge pull request #69 from breezefront/claude/stoic-yonath-58797a

      fix(content-builder): drop fake "spacing" prop from catalog prompt 7db871

    • fix(content-builder): drop fake "spacing" prop from catalog prompt

      The margin/padding summary line started with the group label instead of
      prop names, so the model read "spacing" as a prop and emitted it on every
      component that had one. Render one line per value range, leading with the
      real names, which also stops claiming -200 as the minimum for paddings.

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

    • chore: drop stray .b64 scratch files from screenshots dir

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

    • docs: add screenshots to test guide, correct scenarios after live testing

      Vision-model dropdown pre-filters server-side (10.6 unreachable via UI as
      written), Remove+re-attach confirmed clean (10.9). Found and filed a real
      draft-persistence bug in module-breeze-content-builder (#31) — re-run builds
      report success but don't survive a fresh page reload.

      Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 8062ec

    • docs: add test scenarios and how-to-test guide for Content Builder screenshot import

      Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> 8e6e8d

    • Merge pull request #67 from breezefront/feat/value-backup

      feat(backup): record values before AI writes 70f7cc

    • fix(backup): skip ambiguous staging rows 27e298

    • fix(backup): guard capture reads, batch pruning 19f666

    • feat(backup): record values before AI writes 21d8f6

    • Merge pull request #66 from breezefront/test/bulk-selection-regression

      test(bulk): pin the scope a run resolves to b69352

    • test(bulk): seed the products the scope cases run against

      Copilot was right on both counts: the cases borrowed whatever the
      catalogue happened to hold, and the exclusion case skipped itself whenever
      no two SKUs shared a five-character prefix. A regression check that goes
      quiet when the surrounding data does not suit it is not a guard.

      Three products with a prefix unique to the run are created in setUp and
      removed in tearDown, so every case is deterministic and none can skip. The
      prefix also gets its own case, since the two filtered cases rest on it
      selecting this test's products and nothing else.

      Reverting BulkEntityIdResolver to the pre-33d5e97 behaviour now turns five
      of the six red, up from four. The unfiltered select-all stays green, which
      is correct - that path is the legitimate one and behaved the same before.

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

    • test(bulk): pin the scope a run resolves to

      The selection fault has landed three times. 15c1e7c introduced an
      all_selected flag driven by allSelected(), #31 closed it in June, and
      c13907e swapped in excludeMode() and brought it straight back - Magento
      raises either flag by itself once the checked rows cover the rows the grid
      is showing. Both attempts only ever argued about the client-side flag,
      while the resolver went on loading the collection with no filters at all.

      The E2E spec covers the request the wizard posts. Nothing covered what
      that request then resolves to, which is the number that decides how many
      products get rewritten. These cases do, against the live catalogue: an
      explicit selection, a genuine select-all, a select-all narrowed by a
      filter, exclusions inside a filtered set, and an empty selection.

      Reverting BulkEntityIdResolver to the pre-33d5e97 behaviour turns four of
      the five red, the exception being the unfiltered select-all - which is
      correct, since that case is the legitimate path and behaved the same
      before.

      Refs #61

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

  • 1.2.0
    • Version 1.2.0 634a46

    • Merge pull request #63 from breezefront/feat/build-page-from-screenshot

      Build a Content Builder page from a screenshot 680ede

    • fix(vision): close the second round of review points on #63

      The one that mattered: `options['brief']` skipped the fix from the previous
      commit entirely. It leaves the history untouched by design, so an
      alternating conversation that ended on a user turn got our prompt appended
      after it — the exact pair Anthropic answers 400 to, reachable from REST
      where the caller supplies both a brief and a history. The prompt is now
      folded into a trailing user turn instead of following it, which keeps that
      turn's words rather than dropping them. The class docblock claimed "a
      history that already alternates keeps alternating"; it does now.

      `testSendsTheHistoryFollowedByTheGeneratedPrompt` was pinning the broken
      shape — two user turns, asserted as correct — so its history now ends on
      an assistant turn and the folding case has tests of its own.

      The block hid itself for every reason a build could fail except the two
      the controller actually enforces. A role holding `Magento_Cms::page` but
      not `Swissup_BreezeAi::index` or `Magento_Cms::save` saw a working-looking
      button whose every click came back as the login page, which the modal can
      only report as an expired session.

      Also from the review: the modal kept the previous brief when reopened, so
      it would have ridden the next screenshot unseen; an in-flight FileReader
      could re-arm a screenshot the admin had already removed, now invalidated
      by a token that `clearScreenshot()` moves on; the drop zone gained
      `role="button"` and the brief and model controls gained real labels, since
      a placeholder is not one and the select had no accessible name at all.

      A warning-free build no longer redirects on its own. The draft is saved by
      then but the CMS form behind the modal is not — this request never touched
      it — so a title or URL key typed before opening the modal was silently
      lost. The editor is offered as a link in both outcomes.

      635 unit, 18 integration, 30 e2e green.

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

    • fix(vision): answer the three points Copilot raised on #63

      The docs described a cap the code stopped using. `BuildFromScreenshot`
      reads `post_max_size`, and its own comment explains why `getMaxFileSize()`
      is the wrong ceiling for a base64 string in a text field — the doc still
      named `getMaxFileSize()`, which is exactly the sort of mismatch someone
      debugging a 413 would be misled by.

      The drop zone carries `tabindex="0"` but only answered to a click, so it
      could be tabbed to and not opened. Enter and Space now trigger the picker,
      with the same guard the click handler needs: the remove control sits
      inside the zone and answers to both keys, and swallowing them there would
      leave it dead to the keyboard.

      `getVisionModels()` is asked for four times while one CMS page form
      renders — `canShow`, `getConfigJson`, and `getSelectedModelId` from inside
      it — and each ask loads every model to read its metadata. Memoized for the
      life of the block, which is one request.

      631 tests green; 29 e2e, the new keyboard case among them.

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

    • test(e2e): assert the selection contract the wizard actually posts

      33d5e97 replaced the all_selected flag with Magento's own mass-action
      contract — one of selected or excluded, plus the grid namespace and the
      filters that were on screen — and updated SaveTest, but the Playwright spec
      kept asserting the flag that no longer exists. Red on master since then.

      The assertion now names what the fix was for: the filters travel with the
      selection, which is what stops an exclude-mode Select All from widening past
      the rows the grid was showing. f34845

    • test(e2e): cover the build-from-screenshot modal

      The one surface unit tests cannot reach. It exists because of a bug that got
      all the way to review: the file input is a child of the drop zone, so the
      synthetic click jQuery sends it bubbles back into the zone's own handler and
      recurses until the stack blows — the picker never opens and only drag and drop
      works. "clicking the drop zone opens the file picker" is the guard for that.

      Also covers the pre-flight refusal of a non-image, that the refusal sends no
      request, the preview and remove cycle, and that a reopened modal carries none
      of the previous run's state.

      Stops short of pressing Build draft: that would spend a real provider call.
      Skips itself where the control legitimately hides — no Content Builder, or no
      configured model that reads images. 10934b

    • test(mcp): list build_page among the tools the server exposes

      McpTest asserts a hardcoded tool list written when the MCP endpoint shipped
      with four tools. build_page joined the skill pool in 1bf01f6, and every skill
      in the pool is exposed as a tool, so the integration suite has been red since
      then. Nothing about the server changed — only what the test expects. f8588c

    • feat(vision): build a Content Builder page from a screenshot

      PR #55 landed the whole image path — MessageInterface::getImages(),
      ImageBuilder, ImageValidator, VisionSupport, per-provider encoding — and
      nothing used it. Every caller of build_page passed an empty history, and no
      UI produced an images list. This gives the admin a way in.

      A button in the Content Builder fieldset of the CMS page form opens a modal:
      attach a screenshot, optionally type a brief, pick a vision-capable model.
      The file is read with FileReader and posted as a data URI inside
      history[0].images — nothing is written to disk. Controller/Adminhtml/Page/Build
      runs the skill, saves the result through DraftWriter, and answers with the URL
      of the editor the admin reviews it in.

      Synchronous rather than an option on the bulk wizard: a reference screenshot
      is a per-page input, and one screenshot over N pages produces N near-identical
      layouts. GenerateService, ImageBuilder, ImageValidator, AiRequest, Message and
      all three providers are used exactly as PR #55 built them, unchanged.

      BuildPage now lifts images off the user turns they arrived on and puts them on
      the prompt turn it builds, consuming the last user turn as the brief. That
      fixes an existing fault: Model\Provider\Claude passes the message list through
      unmerged and Anthropic refuses two user turns in a row, so the old
      array_merge($history, [prompt]) answered 400 whenever the lastUserMessage()
      fallback was the thing being used.

      PromptBuilder gains a conditional Reference design block. Two of its five
      rules carry the weight: without the band-per-row line the model answers with a
      flat component list and the row-wrapping rule loses to the picture, and
      without the ignore-brand-and-colours line it copies the screenshot's hex
      values into every colour prop and the draft clashes with the merchant's theme.

      Verified end to end against gpt-4o-mini on a real storefront screenshot:
      six row-wrapped bands, no validator repairs, draft readable in the editor.
      With a brief added, the structure holds and the copy follows the brief rather
      than transcribing the screenshot.

      540 -> 621 unit tests, all green. 9d8854

    • test(mcp): list build_page among the tools the server exposes

      McpTest asserts a hardcoded tool list written when the MCP endpoint shipped
      with four tools. build_page joined the skill pool in 1bf01f6, and every skill
      in the pool is exposed as a tool, so the integration suite has been red since
      then. Nothing about the server changed — only what the test expects. 60d8c7

    • feat(vision): build a Content Builder page from a screenshot

      PR #55 landed the whole image path — MessageInterface::getImages(),
      ImageBuilder, ImageValidator, VisionSupport, per-provider encoding — and
      nothing used it. Every caller of build_page passed an empty history, and no
      UI produced an images list. This gives the admin a way in.

      A button in the Content Builder fieldset of the CMS page form opens a modal:
      attach a screenshot, optionally type a brief, pick a vision-capable model.
      The file is read with FileReader and posted as a data URI inside
      history[0].images — nothing is written to disk. Controller/Adminhtml/Page/Build
      runs the skill, saves the result through DraftWriter, and answers with the URL
      of the editor the admin reviews it in.

      Synchronous rather than an option on the bulk wizard: a reference screenshot
      is a per-page input, and one screenshot over N pages produces N near-identical
      layouts. GenerateService, ImageBuilder, ImageValidator, AiRequest, Message and
      all three providers are used exactly as PR #55 built them, unchanged.

      BuildPage now lifts images off the user turns they arrived on and puts them on
      the prompt turn it builds, consuming the last user turn as the brief. That
      fixes an existing fault: Model\Provider\Claude passes the message list through
      unmerged and Anthropic refuses two user turns in a row, so the old
      array_merge($history, [prompt]) answered 400 whenever the lastUserMessage()
      fallback was the thing being used.

      PromptBuilder gains a conditional Reference design block. Two of its five
      rules carry the weight: without the band-per-row line the model answers with a
      flat component list and the row-wrapping rule loses to the picture, and
      without the ignore-brand-and-colours line it copies the screenshot's hex
      values into every colour prop and the draft clashes with the merchant's theme.

      Verified end to end against gpt-4o-mini on a real storefront screenshot:
      six row-wrapped bands, no validator repairs, draft readable in the editor.
      With a brief added, the structure holds and the copy follows the brief rather
      than transcribing the screenshot.

      540 -> 621 unit tests, all green. c53c71