Recent Activities

This page shows what are we working on.

breezefront / theme-frontend-breeze-enterprise-eir

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

    • 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

breezefront / module-breeze-theme-editor

13 hours ago success
  • 1.0.15
    • Version 1.0.15 712f0f

    • Version 1.0.15 393497

    • feat(auth): make the GraphQL Bearer token header configurable (#31)

      On sites behind HTTP Basic Auth the Theme Editor was unusable: the browser asked
      for the Basic Auth password over and over, and no correct password stopped it.

      The admin UI authenticates its GraphQL calls with `Authorization: Bearer
      <token>`. The web server consumes that header first, tries to read it as Basic
      credentials, fails, and answers `401 WWW-Authenticate: Basic` before Magento
      runs. Every XHR then triggers a native password prompt. A single Authorization
      header cannot carry both Basic credentials and a Bearer token, so this cannot be
      fixed on the client alone.

      Adds `breeze_theme_editor/general/auth_header`, default `Authorization` — no
      behaviour change for existing installs. When set to a custom name (e.g.
      `X-Bte-Authorization`), the admin JS sends the token there and drops
      `Authorization`, and a graphql-area plugin puts the value back into
      `Authorization` right before TokenUserContext reads it.

      Two details the implementation depends on:

      - TokenUserContext receives the shared `App\Request\Http` instance despite its
      `Webapi\Request` type hint. `Webapi\Request` is not shared and gets no
      interceptor generated at all, and `$_SERVER` is snapshotted at bootstrap, so
      the shared request object is the only thing worth mutating.
      - Such servers forward their own `Authorization: Basic ...` to PHP after
      authenticating the browser, so a non-Bearer value is replaced rather than
      skipped. Skipping left the admin unauthenticated.

      Header names that cannot carry the token — the ones this client sets itself
      (Content-Type, X-Requested-With, Store) and the fetch spec's forbidden request
      headers — fall back to `Authorization` instead of silently breaking auth.

      Authentication is unchanged: same JWT, same core validator, same ACL plugin on
      every resolver. Basic Auth is not bypassed — requests without Basic credentials
      still get 401.

      Documents both fixes in the README, including a server-side alternative scoped
      and anchored to the exact GraphQL endpoint (an unanchored rule would let any
      request with an arbitrary Bearer value bypass Basic Auth site-wide). The nginx
      recipe is verified against nginx 1.24 with Basic Auth enabled; the Apache
      variants are documented as untested.

      Verified end to end against a local nginx with auth_basic enabled, and through
      the real admin UI in Chrome. Tests: PHPUnit 898, Jest 992.

      Closes #31 fbd6bd

    • fix(auth): anchor the Basic Auth exception to the exact endpoint (#31)

      Addresses the third review on #32.

      The documented rules were prefix matches, so they widened the bypass beyond the
      endpoint they were meant to scope. Apache <Location> matches by prefix, and both
      the <If> condition and the nginx map used unanchored patterns, so a route such
      as /graphql-admin could opt out of Basic Auth with an arbitrary Bearer value.

      The vhost form now uses <LocationMatch "^/graphql$">, the .htaccess <If> and the
      nginx map are anchored, and the nginx map is case-sensitive with an explicit
      branch for the query string ($request_uri carries it).

      Re-verified against nginx 1.24 with Basic Auth enabled:

      /graphql, /graphql?x=1 with a valid token reach Magento, authenticated
      /graphql-admin, /GraphQL, /, and /graphql
      without an auth header Basic challenge, not bypassed

      The client's 401 snippet had the same problem plus an unescaped path: a base
      path containing regex metacharacters (/shop.v2/graphql) widened the generated
      rule further. The path is now escaped and the pattern anchored. The message also
      hardcoded X-Bte-Authorization, which misreports the header when a different name
      is configured; it interpolates the configured one.

      Not changed: the review also suggested reserving Permissions-Policy in the
      header denylist. That is a response header — it is not in the fetch spec's
      forbidden request headers, so scripts can set it and it would work. Adding it
      would reject a usable value.

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

    • fix(auth): correct the nginx recipe and stop assuming /graphql (#31)

      Addresses the second review on #32.

      The nginx workaround did not work. Magento's routing internally redirects
      /graphql to index.php, the redirected request re-enters the PHP location, and
      that location inherits the server-level auth_basic — so scoping the exception to
      `location = /graphql` changed nothing and the request still got a Basic
      challenge. Verified locally: the previous recipe returns 401.

      The realm is now decided at server level from $request_uri, which survives the
      internal redirect, via three small maps. Verified against nginx 1.24: /graphql
      with a Bearer header reaches Magento (200 with a valid token), while the site
      root with the same header and /graphql without it both still get the Basic
      challenge, so the site stays protected.

      The endpoint path is also no longer assumed to be /graphql. It is built from the
      store base URL, so a subdirectory install answers on /shop/graphql and both the
      documented rules and the snippet printed on a 401 silently did nothing there.
      The client now derives the real path from the configured endpoint and prints it,
      and the README says to substitute it.

      Finally, the browser test runner asserted headers['Authorization'] directly,
      which fails on an editor page configured with a custom header even though the
      client behaves correctly. It now asks ConfigManager which header to check.

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

    • fix(auth): scope the Basic Auth exception and reject unusable headers (#31)

      Addresses the review on #32.

      The documented server-side alternative was unscoped. Apache and nginx can only
      check that the header starts with "Bearer", not that the token is valid, so the
      rule as written let anyone bypass Basic Auth on every route by sending an
      arbitrary Bearer value. Both recipes are now scoped to /graphql, with a warning
      that explains why and notes that /graphql is a public API in Magento. The same
      scoping is applied to the snippet the client prints on a 401.

      Header validation accepted names that cannot carry the token: the client sets
      Content-Type, X-Requested-With and Store itself and would overwrite them, and
      browsers refuse to let scripts set forbidden request headers such as Cookie,
      Host and Origin. Both groups now fall back to Authorization instead of silently
      breaking authentication, as do the Proxy- and Sec- prefixes.

      The 401 message also misdiagnosed the failure once a custom header was already
      configured: a Basic challenge then means the browser sent no valid Basic
      credentials, not that the token was intercepted. It now branches on the
      configured header and says so.

      Verified against the running site: with the header set to Content-Type or
      Cookie, the custom header is ignored and Authorization keeps working.

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

    • feat(auth): make the GraphQL Bearer token header configurable (#31)

      On sites behind HTTP Basic Auth the Theme Editor was unusable: the browser
      asked for the Basic Auth password over and over, and no correct password
      stopped it.

      The admin UI authenticates its GraphQL calls with `Authorization: Bearer
      <token>`. The web server consumes that header first, tries to read it as Basic
      credentials, fails, and answers `401 WWW-Authenticate: Basic` before Magento
      runs. Every XHR then triggers a native password prompt. A single Authorization
      header cannot carry both Basic credentials and a Bearer token, so this cannot
      be fixed on the client alone.

      Add `breeze_theme_editor/general/auth_header`, default `Authorization` — no
      behaviour change for existing installs. When set to a custom name (e.g.
      `X-Bte-Authorization`), the admin JS sends the token there and drops
      `Authorization`, and a graphql-area plugin puts the value back into
      `Authorization` right before TokenUserContext reads it.

      Two details the implementation depends on:

      - TokenUserContext receives the shared `App\Request\Http` instance despite its
      `Webapi\Request` type hint. `Webapi\Request` is not shared and cannot be
      plugged into at all, and `$_SERVER` is snapshotted at bootstrap, so the
      shared request object is the only thing worth mutating.
      - Such servers forward their own `Authorization: Basic ...` to PHP after
      authenticating the browser. Skipping when the header is present would leave
      those credentials for TokenUserContext to choke on, so a non-Bearer value is
      replaced.

      Authentication is unchanged: same JWT, same core validator, same ACL plugin on
      every resolver. Basic Auth is not bypassed — requests without Basic credentials
      still get 401.

      Verified against a local nginx with auth_basic enabled:

      config=Authorization
      basic creds + Bearer in Authorization 401 nginx Basic prompt (the bug)
      basic creds + Bearer in X-Bte-Authorization 403 Magento denied
      config=X-Bte-Authorization
      basic creds + Bearer in X-Bte-Authorization 200 authenticated
      no basic creds 401 nginx Basic prompt

      Also documents both fixes in the README and in the 401 error the client shows.

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

  • 1.0.14
    • Version 1.0.14 4db3c2

    • docs: add QA test plan for COLOR_BACKGROUND field (#30)

      Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> ddaa52

    • docs: add QA test plan for COLOR_BACKGROUND field

      Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> 26b678

breezefront / module-breeze

13 hours ago success
  • head
    • Do not add dom duplicates to $.registry 84beb6

  • 2.32.0
    • Version 2.32.0 62d26e

    • Remove unused `lazyAsync` function 98e9d8

    • Gallery: Fixes slider fallback for mobile devices for `expanded` option eb5ff6

    • Center dots when using expanded layout (on mobile) 88f78f

    • 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

breezefront / module-breeze-content-builder

16 hours ago success
  • head
    • Added Additional HTML prop for Banner component to store animations 7941f0

    • Hover zoom only image/background, keep text content static 27547f

    • Declare the modules BCB actually needs a4923d

    • Fixed composer.json 975c9b

    • Added magento/module-ui dependency

      Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> 154d8a

    • fix(composer): declare the modules BCB actually needs (breezefront/breeze-content-builder#1)

      The metapackage does not need `swissup/module-core` — it arrives
      transitively through this module, exactly as in breezefront/breeze-ai#1.
      What the metapackage cannot fix is a module that under-declares its own
      dependencies, which is what breaks an install on a store that does not
      happen to ship every Magento module. Seven of the modules whose classes
      this one injects or instantiates were missing from `require`:

      - magento/module-catalog — `Block/Adminhtml/Product/Widget/Chooser`
      extends `Magento\Catalog\Block\Adminhtml\Product\Widget\Chooser`, and
      `FeaturedProductRenderer` injects `ProductRepositoryInterface`,
      `Helper\Image` and `Helper\Output`.
      - magento/module-widget — `Widget\Helper\Conditions`
      (`ProductGridRenderer`), `Widget\Model\Widget` and
      `Widget\Model\Widget\Config` (`AdminToolbarPlugin`).
      - magento/module-swatches — `FeaturedProductRenderer` promotes
      `Swatches\ViewModel\Product\Renderer\Configurable` as a constructor
      property, so DI compilation needs it present.
      - magento/module-catalog-inventory — `FallbackProductProvider` injects
      `CatalogInventory\Helper\Stock`.
      - magento/module-variable — `AdminToolbarPlugin` injects
      `Variable\Model\Variable\Config`.
      - magento/module-customer — `BcbContentRenderer` reads
      `Customer\Model\Context::CONTEXT_GROUP` for its cache key.
      - magento/module-configurable-product — `FeaturedProductRenderer` reads
      `Configurable::TYPE_CODE` with no guard.
      - magento/module-newsletter — `NewsletterRenderer` creates
      `Newsletter\Block\Subscribe` with no guard; nothing catches the failure.
      - magento/module-theme — already sequenced in `etc/module.xml`; it was
      the only entry there absent from `require`.

      Also tightened:

      - `php: "^8.1"` added. Promoted `readonly` properties are used throughout
      (`BcbContentRenderer`, every controller, the renderers), so 8.1 is the
      real floor. Matches `swissup/module-breeze` and
      `swissup/module-breeze-theme-editor`.
      - `magento/framework: "*"` → `"^103.0"`. `"*"` guarded nothing; 103.0 is
      the 2.4 line, and it is what `swissup/module-breeze-theme-editor` —
      already required here — asks for.

      Left as is, on purpose:

      - `swissup/module-core: "^1.13.1"` — already the tightest constraint in
      the family (`module-breeze` and `module-breeze-theme-editor` ask for
      `^1.12.27`) and 1.13.1 exists, so it resolves. Not widened.
      - `magento/module-review` and `swissup/module-marketplace` are soft:
      `FeaturedProductRenderer` guards the review block with `class_exists()`
      and `Installer/Command/BcbCmsPage` only names
      `Swissup\Marketplace\Installer\Request` in a docblock. Moved to
      `suggest` rather than `require`.
      - `swissup/module-breeze` stays out. The only coupling is
      `view/frontend/layout/breeze_default.xml`, a handle Magento loads only
      when Breeze is installed; `cms_page_view.xml` carries the rendering on
      any theme.
      - `magento/module-page-builder` and `magento/module-ui` appear in
      comments only.
      - The remaining `magento/module-*: "*"` constraints are untouched — no
      version floor is known to be wrong, and inventing one would be a
      narrower claim than the code supports.

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

    • Merge pull request #36 from breezefront/fix/widget-description-for-ai

      docs(components): stop telling the AI a known widget directive is invented 45a7e6

    • docs(components): stop telling the AI a known widget directive is invented

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

  • 1.2.0
    • Version 1.2.0 7f774f

    • Changes for support of the contact us page 812853

    • Added h1 for the Text component and switched to tags instead of CSS classes e054e6

    • Added new Map component c2d71b

    • Improve Columns vertical stretch 6b5da8

breezefront / module-breeze-ai

16 hours ago success
  • 1.6.0
    • chore(release): 1.6.0

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

  • 1.5.1
    • chore(release): 1.5.1

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

    • fix(content-builder): refuse an ambiguous widget class, keep repairing past a scalar (#91)

      (cherry picked from commit 0780424f1b73d597798af9a4fc49874420870240) d1465f

    • fix(content-builder): key the widget catalog by id (#91)

      (cherry picked from commit daf86df827fbabebaeafb0f502bdfa2245b132d8) 8dba93

    • fix(content-builder): repair a stray backslash in the reply (#91)

      (cherry picked from commit c3b34c496bf6c163120f74142696a55a96704687) 51abb3

    • fix(content-builder): name a widget by its id, not its block class (#91)

      (cherry picked from commit 25d60e8d621ec2ca691c9501668f68d59a207f6a) 4aaba4

    • fix(content-builder): refuse an ambiguous widget class, keep repairing past a scalar (#91) 078042

    • fix(content-builder): key the widget catalog by id (#91) daf86d

    • fix(content-builder): repair a stray backslash in the reply (#91) c3b34c

    • fix(content-builder): name a widget by its id, not its block class (#91) 25d60e

    • fix(content-builder): refuse an ambiguous widget class, keep repairing past a scalar (#91) 692be1

    • fix(content-builder): key the widget catalog by id (#91) 097887

    • fix(content-builder): repair a stray backslash in the reply (#91) c17744

    • fix(content-builder): name a widget by its id, not its block class (#91) 9a0ea1

    • fix(content-builder): match the type spelling the parser accepts (#91) b4085c

    • fix(content-builder): normalize widget types before parsing (#91) 027158

    • fix(image): call OpenAI's image endpoint directly, once (#93) (#102)

      * fix(image): call OpenAI's image endpoint directly, once (#93)

      The SDK could not be used here, and it was costing money rather than elegance.
      Composer allows `^0.10 || ^0.19`; PHP 8.1 — which this module supports — can only
      resolve 0.10, and 0.10 types this response for the pre-`gpt-image-1` API, which
      always carried a URL. So on a supported platform the call reached the paid
      endpoint and then threw while hydrating what came back. The HTTP fallback added
      behind that throw made it worse: two paid calls for one picture, and only one of
      them in the ledger.

      One endpoint and one JSON field is all this ever needed. Plain Guzzle through the
      ClientFactory that is already injected: one code path on every supported PHP
      version, one call per attempt, and the timeout still comes off the request.

      The error mapping is read off the response status rather than out of the message
      now that there is a response to read — 401/403 as the key, 429 as the rate limit,
      400 as a rejected request naming the model and quality, and the body's
      `error.message` for anything else, which beats Guzzle's truncated summary. A
      refusal stays a plain LocalizedException so the caller does not count it against
      the spend cap; only a 200 that carried no usable image is an ImageResponseException,
      which is the charge that did happen.

      Verified against the real endpoint with an invalid key: one HTTP call, and
      "OpenAI: invalid image API key. Check configuration."

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

      * fix(image): count a response timeout as a charge that may have happened (#93)

      ConnectException does not mean the request never went out. cURL reports a
      connect timeout and a response timeout as the same errno 28, and Guzzle files
      both under the same class as a resolution failure — but a response timeout means
      the request was delivered, and OpenAI finishes the picture and bills for it
      whether or not we are still listening. An image takes 10-30 seconds against a
      default budget of 60, so this is an ordinary outcome rather than a corner.

      Told apart by cURL's own handler context: connect_time stays zero until a
      connection exists, so a timeout with a connect time behind it had its request on
      the wire. A resolution, connection or TLS error is never a charge whatever the
      timings say, and a timeout with no context at all — a handler other than cURL —
      is an open question.

      Open questions are answered towards "billed": over-reporting a rare timeout
      costs a cap slot and a ledger line that may not match a charge, while
      under-reporting hides real spend, which is the failure this class was rewritten
      to stop. The message says which of the two happened, so nobody reads the line as
      a confirmed picture.

      Reported by Copilot on PR #102.

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

      * fix(image): decide a failed image call by bytes sent, not by timings (#93)

      Two ways the previous classification still said "unbilled" after the request had
      gone out.

      libcurl reports `connect_time` as zero when it reuses a keep-alive connection,
      and a page generating three images reuses the connection for the second and
      third — so a response timeout on exactly the calls most likely to be billed was
      read as a connect timeout and dropped from the ledger and the cap.

      `CURLE_GOT_NOTHING` is a server that accepted the request and closed without
      answering. Guzzle files it as a connection error like any other, and it is
      post-send by definition, so it was being dropped too.

      Decided on `request_size` now, which is the only direct evidence: anything above
      zero means the request left this machine. Resolution, connection and TLS errors
      never wrote a byte; GOT_NOTHING always did; a timeout is post-send unless the
      byte counts are present and read zero; and an absent context stays an open
      question, answered the conservative way. Timings are not consulted at all.

      Reported by Copilot on PR #102, second pass.

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

      ---------

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

    • fix(image): decide a failed image call by bytes sent, not by timings (#93)

      Two ways the previous classification still said "unbilled" after the request had
      gone out.

      libcurl reports `connect_time` as zero when it reuses a keep-alive connection,
      and a page generating three images reuses the connection for the second and
      third — so a response timeout on exactly the calls most likely to be billed was
      read as a connect timeout and dropped from the ledger and the cap.

      `CURLE_GOT_NOTHING` is a server that accepted the request and closed without
      answering. Guzzle files it as a connection error like any other, and it is
      post-send by definition, so it was being dropped too.

      Decided on `request_size` now, which is the only direct evidence: anything above
      zero means the request left this machine. Resolution, connection and TLS errors
      never wrote a byte; GOT_NOTHING always did; a timeout is post-send unless the
      byte counts are present and read zero; and an absent context stays an open
      question, answered the conservative way. Timings are not consulted at all.

      Reported by Copilot on PR #102, second pass.

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

    • fix(content-builder): read a directive backslash as a separator (#91) da1323

    • fix(image): count a response timeout as a charge that may have happened (#93)

      ConnectException does not mean the request never went out. cURL reports a
      connect timeout and a response timeout as the same errno 28, and Guzzle files
      both under the same class as a resolution failure — but a response timeout means
      the request was delivered, and OpenAI finishes the picture and bills for it
      whether or not we are still listening. An image takes 10-30 seconds against a
      default budget of 60, so this is an ordinary outcome rather than a corner.

      Told apart by cURL's own handler context: connect_time stays zero until a
      connection exists, so a timeout with a connect time behind it had its request on
      the wire. A resolution, connection or TLS error is never a charge whatever the
      timings say, and a timeout with no context at all — a handler other than cURL —
      is an open question.

      Open questions are answered towards "billed": over-reporting a rare timeout
      costs a cap slot and a ledger line that may not match a charge, while
      under-reporting hides real spend, which is the failure this class was rewritten
      to stop. The message says which of the two happened, so nobody reads the line as
      a confirmed picture.

      Reported by Copilot on PR #102.

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

    • fix(image): call OpenAI's image endpoint directly, once (#93)

      The SDK could not be used here, and it was costing money rather than elegance.
      Composer allows `^0.10 || ^0.19`; PHP 8.1 — which this module supports — can only
      resolve 0.10, and 0.10 types this response for the pre-`gpt-image-1` API, which
      always carried a URL. So on a supported platform the call reached the paid
      endpoint and then threw while hydrating what came back. The HTTP fallback added
      behind that throw made it worse: two paid calls for one picture, and only one of
      them in the ledger.

      One endpoint and one JSON field is all this ever needed. Plain Guzzle through the
      ClientFactory that is already injected: one code path on every supported PHP
      version, one call per attempt, and the timeout still comes off the request.

      The error mapping is read off the response status rather than out of the message
      now that there is a response to read — 401/403 as the key, 429 as the rate limit,
      400 as a rejected request naming the model and quality, and the body's
      `error.message` for anything else, which beats Guzzle's truncated summary. A
      refusal stays a plain LocalizedException so the caller does not count it against
      the spend cap; only a 200 that carried no usable image is an ImageResponseException,
      which is the charge that did happen.

      Verified against the real endpoint with an invalid key: one HTTP call, and
      "OpenAI: invalid image API key. Check configuration."

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

    • test(content-builder): cover the escape repair edges (#91) 5e8089

    • test(content-builder): pin the escape error the repair keeps (#91) ed702d

    • feat(content-builder): generate an image where the design has only a placeholder (#93) (#100)

      * feat(image): tell a wireframe stand-in from a flat design (#93)

      PlaceholderDetector cannot be the trigger for generating a picture, and the
      reason is what its answer costs. There, a false positive keeps the
      dummyimage.com URL the build already used; in front of generation, a false
      positive paints an invention over a band a designer meant to be flat — a
      brand-coloured hero, a dark call-to-action strip — and bills for it. So the
      question has to be a different one, asked of positive evidence: not "is this
      crop empty" but "is this a wireframe at all".

      Answered of the whole screenshot, once per build, because a grey box in a mock
      and a grey box in a design are indistinguishable at the size of one crop. Two
      signals have to agree: chroma, which a design's photography or brand colour
      pushes up, and luminance entropy, which is what stops a black-and-white
      photographic design being read as a mock. A crop's own drawn marks — a cross,
      an outlined frame — are the second way in, for the design that is finished
      apart from the one band still holding a box.

      Measured against real files: this module's five-band mock lands at chroma 0.014
      and entropy 1.9, a storefront screenshot with photography at chroma 0.32, a
      photograph at entropy 5.1.

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

      * feat(content-builder): draw the picture a wireframe left a box for (#93)

      The other half of issue #85. Extraction answers a screenshot of a real design
      and answers nothing at all for a wireframe, where the region genuinely is a grey
      box and the merchant replaces the placeholder by hand.

      Second, not first, and the order is the point: extraction is free and exact,
      while one generated image costs $0.01 to $0.25 against about $0.03 for the whole
      text build that placed it. So generation runs only where extraction has already
      declined, and only where a prop passes every gate — the setting is on, the prop
      is a band's main image rather than an icon, the build call itself described a
      missing picture there, and WireframeDetector agrees the reference is a stand-in.
      Off by default, with a hard per-build cap that is a spend limit rather than a
      tidiness one, hence no "unlimited" at zero.

      The descriptions ride back on the build call like the regions do, so this costs
      no request of its own. An image model is configured on its own — Claude
      generates no images, so the text model says nothing about who can draw one — and
      lives in its own pool rather than as a row that could become the default a chat
      call routes to.

      Nothing here can cost the build: a refusal, a timeout, a missing key, a full
      disk or the cap each leave the dummyimage.com placeholder in place and say so in
      the build note, one note per prop rather than two contradicting each other.
      Every call that answered is written to the usage ledger as kind=image, priced
      through the same table the admin's pre-build estimate is priced through, so a
      page's cost is complete exactly where it is largest.

      The crop is deliberately not sent as a reference image. Under these gates it is
      the wireframe's own grey box: nothing to imitate, and billed as input. Its shape
      is used instead, to pick the size.

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

      * feat(admin): say what a build may spend on images before it starts (#93)

      A ceiling, not a forecast, and it cannot be otherwise: how many pictures a page
      needs is only known once the build call has answered, since the descriptions
      ride back on it. So the modal and the editor's chat panel show the most the run
      can cost — the cap times the rate — which is the number the cap exists to make
      true. Priced through the same ImagePrice the ledger writes, so the estimate and
      the invoice cannot disagree.

      One sentence, composed once and shared by both controls, which compose their own
      config JSON and would otherwise drift apart.

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

      * fix(content-builder): a free image rate is not an unknown one (#93)

      The page total read "cost is not known" off the numeric total rather than off
      whether a rate was found, so a store on a zero per-image rate — an internal
      proxy, a plan with images included, the override set to 0, all of which
      Helper\Config deliberately accepts — was told its pictures cost an unknown
      amount instead of nothing. Gate the wording on costKnown, which is the thing
      that actually tracks it.

      While there: an amount under a cent is written with four places rather than two
      in both the build note and the pre-build estimate. Two writes $0.0040 as $0.00,
      which in this line reads as "the picture was free" — the same claim Usage\Cost
      keeps its own precision to avoid.

      Reported by Copilot on PR #100.

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

      * fix(image): recognise OpenAI's key and rate-limit errors by their wording (#93)

      A live run against the real endpoint with a deliberately bad key came back as
      "OpenAI image error: Incorrect API key provided: sk-…", not as the intended
      "invalid image API key. Check configuration." The SDK raises its own exception
      carrying the API's message body and nothing else, so a check for '401' never
      matches — the one failure an admin can act on was reported as a generic error.

      Match the wording as well as the status, and the same for the rate limit.

      Model\Provider\OpenAi::handleException() has the identical blind spot for chat
      calls. Left alone here: it is not this issue's code, and the fix belongs with a
      run that observes it the same way this one was observed.

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

      * fix(content-builder): stop telling the model a stand-in is already handled (#93)

      A live run explained the silence. With generation on, every gate open and the
      description rules in the prompt, a small model returned no image_prompts at all
      on a wireframe — because the region rules, which sit closer to the coordinates
      it was writing, ended with "leave those props out of image_regions and let the
      placeholder rule above fill them". A model that believes a prop is taken care of
      has no reason to describe it.

      So that clause now has two versions and the build picks by whether anything will
      draw the picture: point at the placeholder when nothing will, and at
      image_prompts when something will, naming the consequence of saying nothing. The
      ask itself is stated as a requirement rather than as an offer for the same
      reason.

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

      * fix(image): address 5 Copilot review issues on PR #100 (#93)

      - Gemini: catch ConnectException before it bypasses handleException and
      leaks ?key= credential in server log (ConnectException is not a
      RequestException subclass)

      - WireframeDetector: convert palette/indexed images to truecolor before
      per-pixel sampling; imagecolorat() returns a palette index on GIF and
      indexed PNG, not packed RGB, so a coloured palette image was read as
      achromatic and misclassified as a wireframe

      - PromptBuilder: REGION_STAND_IN_RULE_GENERATING now asks the model to
      still report image_regions for stand-in props (for shape/mark
      detection) and describe them in image_prompts — the previous wording
      told the model to omit the region, making the crop-marks gate
      unreachable for isolated placeholders in finished designs

      - ImageExtractor: take a crop in memory when extraction is off but
      generation is on and the page is not already a wireframe, so the
      crop-marks gate works in generation-only mode; skip the crop when the
      wireframe verdict already covers the whole page

      - ImageExtractor/ImageGenerator: track billable API attempts separately
      from successful saves; a storage failure after the provider answered
      no longer reopens a generation slot above the admin-configured cap

      * fix(test): replace assert() with assertNotFalse() — assert() forbidden by Magento CS (#93)

      * fix(image): address 4 more Copilot review issues on PR #100 (#93)

      - ImageResponseException: new exception subclass for 'provider answered
      but no image' (HTTP 200, refusal, bad base64) — distinct from
      LocalizedException so ImageGenerator can count it as billed without
      treating every auth rejection and rate-limit error as billable too

      - ImageGenerator: catch ImageResponseException as billed (writeImage +
      billed=true); catch everything else as not-billed (provider never
      reached); mb_substr/mb_strlen for multibyte prompt truncation so a
      multilingual description is not split mid-codepoint

      - PromptBuilder: IMAGE_PROMPT_RULES last item rewritten — stand-ins get
      both image_regions (shape/mark detection) AND image_prompts
      (description); the 'never both' rule was directly contradicting the
      REGION_STAND_IN_RULE_GENERATING that this PR introduced

      - ImageExtractor: add 'placeholder' flag to attempt struct so
      reportUnfilled() can distinguish 'PlaceholderDetector rejected this
      crop' from 'cropBytes() took a crop for stand-in detection only';
      the old attempt['crop'] !== null check was true for both and falsely
      told the admin the reference showed a placeholder on any non-wireframe
      page where generation declined for a different reason

      * fix(openai): HTTP fallback for PHP 8.1 + openai-php 0.10 (#93)

      openai-php 0.10 (the only version PHP 8.1 can install) throws a
      TypeError when hydrating gpt-image-1's base64 response. The paid
      API call was wasted. Bypass the SDK and call the REST endpoint
      directly via Guzzle so the call still produces an image.

      ---------

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

    • fix(openai): HTTP fallback for PHP 8.1 + openai-php 0.10 (#93)

      openai-php 0.10 (the only version PHP 8.1 can install) throws a
      TypeError when hydrating gpt-image-1's base64 response. The paid
      API call was wasted. Bypass the SDK and call the REST endpoint
      directly via Guzzle so the call still produces an image. d675d0

    • fix(image): address 4 more Copilot review issues on PR #100 (#93)

      - ImageResponseException: new exception subclass for 'provider answered
      but no image' (HTTP 200, refusal, bad base64) — distinct from
      LocalizedException so ImageGenerator can count it as billed without
      treating every auth rejection and rate-limit error as billable too

      - ImageGenerator: catch ImageResponseException as billed (writeImage +
      billed=true); catch everything else as not-billed (provider never
      reached); mb_substr/mb_strlen for multibyte prompt truncation so a
      multilingual description is not split mid-codepoint

      - PromptBuilder: IMAGE_PROMPT_RULES last item rewritten — stand-ins get
      both image_regions (shape/mark detection) AND image_prompts
      (description); the 'never both' rule was directly contradicting the
      REGION_STAND_IN_RULE_GENERATING that this PR introduced

      - ImageExtractor: add 'placeholder' flag to attempt struct so
      reportUnfilled() can distinguish 'PlaceholderDetector rejected this
      crop' from 'cropBytes() took a crop for stand-in detection only';
      the old attempt['crop'] !== null check was true for both and falsely
      told the admin the reference showed a placeholder on any non-wireframe
      page where generation declined for a different reason 362f3d

    • fix(test): replace assert() with assertNotFalse() — assert() forbidden by Magento CS (#93) 4ad84b

    • fix(image): address 5 Copilot review issues on PR #100 (#93)

      - Gemini: catch ConnectException before it bypasses handleException and
      leaks ?key= credential in server log (ConnectException is not a
      RequestException subclass)

      - WireframeDetector: convert palette/indexed images to truecolor before
      per-pixel sampling; imagecolorat() returns a palette index on GIF and
      indexed PNG, not packed RGB, so a coloured palette image was read as
      achromatic and misclassified as a wireframe

      - PromptBuilder: REGION_STAND_IN_RULE_GENERATING now asks the model to
      still report image_regions for stand-in props (for shape/mark
      detection) and describe them in image_prompts — the previous wording
      told the model to omit the region, making the crop-marks gate
      unreachable for isolated placeholders in finished designs

      - ImageExtractor: take a crop in memory when extraction is off but
      generation is on and the page is not already a wireframe, so the
      crop-marks gate works in generation-only mode; skip the crop when the
      wireframe verdict already covers the whole page

      - ImageExtractor/ImageGenerator: track billable API attempts separately
      from successful saves; a storage failure after the provider answered
      no longer reopens a generation slot above the admin-configured cap 403bca

    • fix(content-builder): survive an unescaped widget type (#91) 2df025

    • fix(content-builder): stop telling the model a stand-in is already handled (#93)

      A live run explained the silence. With generation on, every gate open and the
      description rules in the prompt, a small model returned no image_prompts at all
      on a wireframe — because the region rules, which sit closer to the coordinates
      it was writing, ended with "leave those props out of image_regions and let the
      placeholder rule above fill them". A model that believes a prop is taken care of
      has no reason to describe it.

      So that clause now has two versions and the build picks by whether anything will
      draw the picture: point at the placeholder when nothing will, and at
      image_prompts when something will, naming the consequence of saying nothing. The
      ask itself is stated as a requirement rather than as an offer for the same
      reason.

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

    • fix(image): recognise OpenAI's key and rate-limit errors by their wording (#93)

      A live run against the real endpoint with a deliberately bad key came back as
      "OpenAI image error: Incorrect API key provided: sk-…", not as the intended
      "invalid image API key. Check configuration." The SDK raises its own exception
      carrying the API's message body and nothing else, so a check for '401' never
      matches — the one failure an admin can act on was reported as a generic error.

      Match the wording as well as the status, and the same for the rate limit.

      Model\Provider\OpenAi::handleException() has the identical blind spot for chat
      calls. Left alone here: it is not this issue's code, and the fix belongs with a
      run that observes it the same way this one was observed.

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

    • fix(content-builder): a free image rate is not an unknown one (#93)

      The page total read "cost is not known" off the numeric total rather than off
      whether a rate was found, so a store on a zero per-image rate — an internal
      proxy, a plan with images included, the override set to 0, all of which
      Helper\Config deliberately accepts — was told its pictures cost an unknown
      amount instead of nothing. Gate the wording on costKnown, which is the thing
      that actually tracks it.

      While there: an amount under a cent is written with four places rather than two
      in both the build note and the pre-build estimate. Two writes $0.0040 as $0.00,
      which in this line reads as "the picture was free" — the same claim Usage\Cost
      keeps its own precision to avoid.

      Reported by Copilot on PR #100.

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

    • feat(admin): say what a build may spend on images before it starts (#93)

      A ceiling, not a forecast, and it cannot be otherwise: how many pictures a page
      needs is only known once the build call has answered, since the descriptions
      ride back on it. So the modal and the editor's chat panel show the most the run
      can cost — the cap times the rate — which is the number the cap exists to make
      true. Priced through the same ImagePrice the ledger writes, so the estimate and
      the invoice cannot disagree.

      One sentence, composed once and shared by both controls, which compose their own
      config JSON and would otherwise drift apart.

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

    • feat(content-builder): draw the picture a wireframe left a box for (#93)

      The other half of issue #85. Extraction answers a screenshot of a real design
      and answers nothing at all for a wireframe, where the region genuinely is a grey
      box and the merchant replaces the placeholder by hand.

      Second, not first, and the order is the point: extraction is free and exact,
      while one generated image costs $0.01 to $0.25 against about $0.03 for the whole
      text build that placed it. So generation runs only where extraction has already
      declined, and only where a prop passes every gate — the setting is on, the prop
      is a band's main image rather than an icon, the build call itself described a
      missing picture there, and WireframeDetector agrees the reference is a stand-in.
      Off by default, with a hard per-build cap that is a spend limit rather than a
      tidiness one, hence no "unlimited" at zero.

      The descriptions ride back on the build call like the regions do, so this costs
      no request of its own. An image model is configured on its own — Claude
      generates no images, so the text model says nothing about who can draw one — and
      lives in its own pool rather than as a row that could become the default a chat
      call routes to.

      Nothing here can cost the build: a refusal, a timeout, a missing key, a full
      disk or the cap each leave the dummyimage.com placeholder in place and say so in
      the build note, one note per prop rather than two contradicting each other.
      Every call that answered is written to the usage ledger as kind=image, priced
      through the same table the admin's pre-build estimate is priced through, so a
      page's cost is complete exactly where it is largest.

      The crop is deliberately not sent as a reference image. Under these gates it is
      the wireframe's own grey box: nothing to imitate, and billed as input. Its shape
      is used instead, to pick the size.

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

    • feat(image): tell a wireframe stand-in from a flat design (#93)

      PlaceholderDetector cannot be the trigger for generating a picture, and the
      reason is what its answer costs. There, a false positive keeps the
      dummyimage.com URL the build already used; in front of generation, a false
      positive paints an invention over a band a designer meant to be flat — a
      brand-coloured hero, a dark call-to-action strip — and bills for it. So the
      question has to be a different one, asked of positive evidence: not "is this
      crop empty" but "is this a wireframe at all".

      Answered of the whole screenshot, once per build, because a grey box in a mock
      and a grey box in a design are indistinguishable at the size of one crop. Two
      signals have to agree: chroma, which a design's photography or brand colour
      pushes up, and luminance entropy, which is what stops a black-and-white
      photographic design being read as a mock. A crop's own drawn marks — a cross,
      an outlined frame — are the second way in, for the design that is finished
      apart from the one band still holding a box.

      Measured against real files: this module's five-band mock lands at chroma 0.014
      and entropy 1.9, a storefront screenshot with photography at chroma 0.32, a
      photograph at entropy 5.1.

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

breezefront / theme-frontend-breeze-enterprise-apollo

21 hours ago success

swissup / module-marketplace

21 hours ago success
  • head
    • Typo fix 50a7b6

    • Cleanup CSS ea255a

    • Fix grid styles 4d48e9

    • Stop running composer from the backend (#17)

      Running composer through the web server is not reliable, so the backend no
      longer dispatches composer operations. Install, Update, Remove, Enable and
      Disable now open a modal with the command to copy and run in a terminal, both
      for a single row and for a massaction. Run Installer stays a real backend
      operation - the one-click installer does not use composer.

      Enable and Disable had no console equivalent, so marketplace:package:enable and
      marketplace:package:disable were added (aliases marketplace:enable and
      marketplace:disable).

      Channel configuration and Tasks History are removed from the backend as well.
      Channels are managed with the marketplace:channel:* and marketplace:auth:*
      commands. With the settings form gone, ChannelsSave was the last job producer,
      so the whole queue is removed: JobDispatcher, QueueDispatcher, Validator, the
      Job entity, the activity widget, the queue crons and the job table.

      The SettingsDataProvider modifiers are kept as empty stubs so that the channel
      modules registering them in the pool keep compiling.

      BREAKING: the swissup_marketplace_job table is dropped on setup:upgrade, and
      HandlerInterface::validateBeforeDispatch() is removed - it had no callers left.

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

    • Ignore the packages with an invalid name

      The channel metadata is not validated, and the package name ends up in
      the command we suggest to copy and run. A name containing ;, $() or a
      whitespace turns that command into something else.

      Filtered where the channel data is read, so that such a package never
      reaches the grid, the suggested command, or the installer request. The
      pattern is the one composer validates the names with, so nothing that
      composer is able to install is dropped.

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

    • Improve package disable/enable operations cc8491

    • Address review comments

      - Massaction: require the row to expose the action before offering a
      command. Local.php marks every downloaded package as enabled and only
      overrides it for the modules, so a downloaded theme passed the shared
      disable check even though Links.php never adds Enable/Disable to a
      non-module row - marketplace:disable would then write a derived, bogus
      module name to config.php.
      - Copy button: writeText() is asynchronous and rejects when the clipboard
      is blocked. Copy now returns a deferred, falls back to execCommand on
      rejection, and the Copied label is shown only once a copy succeeded.
      - README grammar.

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

    • Stop running composer from the backend

      Running composer through the web server was never reliable, so the backend no
      longer dispatches composer operations. Install, Update, Remove, Enable and
      Disable now open a modal with the command to copy and run in a terminal, both
      for a single row and for a massaction. Run Installer stays a real backend
      operation - the one-click installer does not use composer.

      Enable and Disable had no console equivalent, so marketplace:package:enable and
      marketplace:package:disable were added (aliases marketplace:enable and
      marketplace:disable).

      Channel configuration and Tasks History are removed from the backend as well.
      Channels are managed with the marketplace:channel:* and marketplace:auth:*
      commands. With the settings form gone, ChannelsSave was the last job producer,
      so the whole queue is removed: JobDispatcher, QueueDispatcher, Validator, the
      Job entity, the activity widget, the queue crons and the job table.

      The SettingsDataProvider modifiers are kept as empty stubs so that the channel
      modules registering them in the pool keep compiling.

      BREAKING: the swissup_marketplace_job table is dropped on setup:upgrade, and
      HandlerInterface::validateBeforeDispatch() is removed - it had no callers left.

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

    • Forward command output strings to terminal c17318

    • Allow using dry-run option 2d329d

    • Reuse composer_cache from user's directory if possible 9cc10a

    • Command shortcuts 2cfa4c

  • 1.11.0
    • Version 1.11.0 e5e9b8

    • Fixed PHP stan return type errors 49ac60

    • Allow to run installer for themes missing in composer.lock 7bb9ca

swissup / theme-frontend-argentohyva-stripes

1 day ago error

breezefront / breeze-blank

1 day ago success

breezefront / theme-frontend-breeze-enterprise-blank

1 day ago success