Recent Activities
This page shows what are we working on.
-
head
-
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
-
Add EasySlide functionality for header panel with custom slides and styles e55b0b
-
-
head
-
1.0.3
-
head
-
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
-
-
1.5.0
-
chore(release): 1.5.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> e0b3b7 -
Merge pull request #97 from breezefront/fix/96-field-actions-layout
fix(admin): keep AI actions off the field's scope row (#96) 49631b -
fix(uninstall): honour delete()'s bool channel as a failure (#96)
WriteInterface::delete() reports failure two ways: the stock Write throws, but
the interface declares a bool and a remote-storage implementation is free to
answer plainly false. Ignoring the return let a false slip through and the
uninstall carry on into dropTable(), leaving the crops on disk with the schema
already gone — the exact half-removed state removing media first was meant to
rule out. Treat both channels as the same failure.
Not reachable on a stock install, where Write::delete() only ever returns true
or throws. It is reachable through any other WriteInterface, and the guarantee
is worth only as much as its weakest branch. 47f66b -
fix(uninstall): take the generated crops too, and narrow the JS claim (#96)
Second review round, both points real.
--remove-data left pub/media/breezeai behind. MediaStorage owns that directory
outright, and Prune — the job that collects crops no draft points at — leaves
with the module, so anything kept behind would sit there uncollected forever
under a command that claimed a full purge. Delete it, before the schema rather
than after: an unwritable media directory is the likeliest failure here, and
failing first leaves the install intact and retriable instead of tableless with
its images still on disk. A failure is reported, not swallowed — the files are
the one thing an uninstall cannot recreate.
Saying so also means saying what it costs: pages built from a screenshot
reference those crops and outlive the module, so the doc now spells out that
--remove-data breaks their images, and that omitting the flag skips this class
entirely for anyone who wants them kept.
The doc also claimed no module JavaScript loads while the module is disabled.
requirejs-config.js registers bcb-chat-mixin against the Content Builder panel
unconditionally, so the mixin and its dependencies are still fetched there; they
read an empty config and return without injecting anything. The guarantee is
about injection, not loading, and it now says that. 1c073c -
fix(uninstall): escape the LIKE prefix, and stop overpromising (#96)
Four points from review, all of them real.
`_` is a single-character wildcard in LIKE, so `swissup_breezeai/%` also matched
paths like `swissupXbreezeai/...` — an uninstall could have taken another
module's configuration with it. Escape the metacharacters and declare the escape
character in the statement, using `!` rather than a backslash so the result does
not depend on sql_mode=NO_BACKSLASH_ESCAPES.
The E2E spec constructed admin URLs without the per-route secret key. Links on
this stand do carry one, and an instance that validates it would have timed the
spec out instead of testing anything. Read the URL out of the DOM, the way
bulk-action.spec.js and page-builder.spec.js already do.
Two claims in the removal doc were wrong. The Enabled flag suppresses what the
module renders into someone else's form; it does not hide the module's own menu,
which menu.xml declares unconditionally and gates by ACL. And removing the
package cannot on its own fix a preference declared elsewhere: the compiler
checks class_exists() before it checks whether the class was scanned, so taking
the code out only swaps "has not been included in dependency injection
compilation" for "does not exist". The declaring source has to go first. 3f1bed -
fix(admin): keep AI actions off the field's scope row (#96)
.admin__field-control holds more than the input. Magento renders the error
label, the note, additional info and — at store-view scope only — the
admin__field-service block carrying "Use Default Value" into it as siblings of
the field. Making that container a flex row for the AI actions (#82) hoisted all
of them onto the field's line; the field, at flex-basis 0, then collapsed to a
sliver and its value read as empty. A merchant on 2.4.9 reported a store-scoped
Text Area attribute rendering blank with the scope checkbox displaced, and
rolled back to 1.2.0.
Give every child its own full-width row, then opt the field and the actions back
onto a shared first row. That keeps what #82 asked for and puts everything else
back where it sits with no actions at all.
Also ship the removal path the same report asked for: Setup\Uninstall drops the
module's tables and configuration so module:uninstall --remove-data leaves
nothing behind, and docs/UNINSTALL.md covers both turning the module off and
taking it out, including why a disabled module's own di.xml cannot be the source
of a preference the 2.4.9 compiler rejects. 9c5b39 -
feat(content-builder): let the AI place Magento widgets (#91) (#95)
* feat(content-builder): let the AI place Magento widgets (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(content-builder): describe only the enabled widgets, and offer no select we cannot list (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(content-builder): write the widget defaults the prompt promises, and report what a directive could not carry (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> dd87e3 -
fix(content-builder): write the widget defaults the prompt promises, and report what a directive could not carry (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> d652df -
fix(content-builder): describe only the enabled widgets, and offer no select we cannot list (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 60f747 -
feat(content-builder): let the AI place Magento widgets (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 5be597 -
Merge pull request #94 from breezefront/fix/vision-test-post-max-size
test(vision): let a test narrow the post_max_size setUp established ee92e8 -
fix(content-builder): describe only the enabled widgets, and offer no select we cannot list (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> bbfe20 -
test(vision): let a test narrow the post_max_size setUp established
The first willReturn matcher answered every invocation, so the second one
a test body added never fired and the cap test asserted against 64M.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> a5fccd -
feat(content-builder): let the AI place Magento widgets (#91)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 3a19b3 -
Merge pull request #92 from breezefront/feat/85-image-extraction
feat(content-builder): take a generated page's images from the screenshot (#85) 8b2d79 -
fix(image): scan a trimmed edge exactly, and compare whole origins (#85)
Round four. Four of the five inline comments repeat findings already fixed in
the two commits before this one; these are what was left.
The trim sampled every Nth pixel along an edge, which is safe for a
photograph's flat margin and wrong for the one case the trim exists to serve: a
line icon's stroke is a pixel or two wide, can fall between samples, and the
edge then reads as empty and the outline is cut off the icon. A crop small
enough to be an icon is now scanned pixel by pixel; the sampled path stays for
anything larger.
The media base URL was compared to the web base URL by host alone, so media on
https://shop.test:8443/ looked local to a storefront on the default port and
was written as a root-relative path that resolves against the wrong server.
Scheme, host and port are compared together.
Prune read every matching content document into memory before picking the file
names out of them. It streams the rows instead — the names are forty
characters each, the documents are whole pages.
Both descriptions of the setting said that turning it off makes every image a
placeholder. Since the brief's own images are now left alone, that is no longer
what happens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 47ab31 -
fix(content-builder): keep repaired icons in their band, and check GD first (#85)
Round three of the review.
Clamping a box to its band and then squaring it up put it back outside: an
icon against a section edge grew through the boundary and cropped pixels from
the section next door. It is moved back inside rather than cut down, because
cutting it down restores exactly the flattened box the repair exists to
rescue. A box the grid flattened onto a single line is also kept now when that
line falls inside the band — intersecting a zero-height box with anything
leaves nothing, so the band check was throwing away the very shape squaredUp()
was written for.
WebP is an optional part of GD and every crop ends in an imagewebp() call, so
a server built without it failed on the last step of each crop and kept every
placeholder without saying why. The setting answers false there, once, with
the reason in the log — and the prompt then does not spend tokens asking for
coordinates nothing can use.
The base screenshot rule lost the word "logo" when it was rescoped, which left
an extraction-disabled build without the instruction it had before this branch
existed. Restored.
Prune's per-run cap now counts what it removes rather than what it looks at.
Skipping a file costs a stat and an array lookup since the reference lookup
became one scan per table, so spending the budget on files that were never
going away only let them hide the collectable ones behind them, in the same
order, every night.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> f0884f -
fix(content-builder): keep the brief's images, and judge crops by the prop (#85)
Four from the second review round.
The stand-in host was matched with str_ends_with, so notdummyimage.com — a
domain belonging to somebody else — counted as ours and an icon supplied from
it would have had its switch closed. Matched as the host itself or a subdomain
of it now.
A crop's kind was decided by its size alone. The symbol thresholds are close to
"anything but a blank square", which is what a monochrome line icon needs, and
a wireframe's grey box with a cross through it is small enough to be handed
them — so the one thing the detector exists to reject sailed past it. Size
still decides how a region is cut; what the result is allowed to be is decided
by the prop, and only a prop named for an icon is judged as a symbol.
A background image the brief had already supplied was overwritten by the band
fallback whenever the model reported no region for it. Naming an image is a
decision and a region is a guess about where one is, so extraction now fills
only a prop that is empty or still holds the generated stand-in — every image
prop, not only the background that exposed it.
Prune asked "is this file referenced" once per file: three LIKE queries against
unindexed text columns, up to fifteen hundred full scans in one cron run on a
backlog. It reads the names back out of the content instead, once per table per
run, and not at all on a night with nothing old enough to remove.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 933f93 -
fix(content-builder): choose the screenshot, and the switch, deliberately (#85)
Two more from the review, both about a decision that was being made by
accident.
BuildPage hands over every image the conversation carries, and extraction cut
from the first of them. In the screenshot modal there is only ever one, but in
the editor's chat panel the reference the admin has just sent sits behind the
one they sent an hour ago — so the model described the new design and the crops
came out of the old one, silently. A region names no image and nothing here can
tell them apart, so the newest is cropped as the best available guess, and the
build says so when there was more than one to choose from.
The other is the icon case. Filling an image prop with a dummyimage.com box is
right where the prop always renders: the merchant sees a labelled grey
rectangle and knows to replace it. Behind a switch it is not, because the
component offers a better answer — a feature card without an icon, which is
what the switch's own default says. The stand-in now counts as unfilled there,
and the host it is recognised by is the constant the prompt is written from
rather than a second copy of the string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 4c31e2 -
fix(content-builder): answer the review on extracted images (#85)
Six things the review found, none of which change what a good run produces:
- A background whose box was unusable fell back to its band and got its
picture, but the failed box had already added a warning telling the merchant
to set the image in the editor. The fallback is resolved first, and the
warning is left for the case where nothing was found.
- The symbol threshold was two colours, which is what a crisp monochrome icon
quantises to. Its own docblock says "no second colour worth the name" — one.
The WebP round trip usually adds enough noise to hide this, so it cost an
icon only where the screenshot drew one cleanly.
- Reusing a stored crop kept the old mtime, so a draft could adopt a file
already past the retention window and have it pruned before the page was
saved. A reused crop is touched.
- Prune spent its per-run budget on files it had already decided to keep,
which let a directory of recent crops hide the old ones behind them on every
run. Only files past the cutoff count against it.
- The upload limit bounds compressed bytes, not the bitmap they unpack into.
The measurement already happens before anything decodes, so an implausible
pixel area is refused there rather than in the allocation after it.
- The README promised a warning for every unfilled image prop. A prop the
design said nothing about passes silently, and that is the normal wireframe
case, so the sentence now says what the code does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6e71f8 -
test(image): read the WebP signature instead of silencing getimagesize (#85)
The Magento2 standard rejects the error suppression, and the assertion did
not need it: the question is what bytes were written, which the RIFF/WEBP
container signature answers on its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> c02438 -
feat(content-builder): take a generated page's images from the screenshot (#85)
A page built from a real design came back with a dummyimage.com box wherever
the design had a photo, and the admin replaced every one by hand. The merchant
already handed us the picture: the model is now asked, on the build call that
was happening anyway, where each image is, and the region is cropped out of the
screenshot locally and stored in media.
Two coordinate sources, because neither works alone. A per-prop box is the only
thing that can locate an icon and is loose about it; a band's vertical extent is
coarse but dependable. So a box is held to its own band before it is used — a
hero box spanning three bands was the common failure on a tall screenshot — a
box the 0..1000 grid flattened is squared up on its surviving axis, a small box
is padded and trimmed back onto its own content, and a background image falls
back to the band strip when no usable box came back.
A crop that turns out to hold a wireframe placeholder is skipped and the
dummyimage URL kept: a grey rectangle stored as the page's hero is worse than
the placeholder it replaced. Symbols are judged by their own thresholds, since a
line icon is monochrome by design. Generating an image for those regions is the
separate second half of #85.
Model/Image carries no Content Builder dependency — the per-band build loop of
#84 crops the same screenshot with the same services. 53eece
-
-
3.3.1
-
3.3.0
-
Version 3.3.0 7f1f00
-
Added Contact Us page installer a791e7
-
Improve switcher label alignment inside mobile slideout a6415d
-
Fixed transparent menu icon for few seconds on slow web connection 7b8ab8
-
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.3.2
-
3.3.1
-
3.2.2
-
3.2.1
-
head
-
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
-
head
-
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
-
1.4.1