Untagged Commits
This page helps make in-time deliveries for all new goodies.
breezefront / module-breeze-theme-editor
1 commit 1-
head
-
docs: add QA test plan for COLOR_BACKGROUND field (#30)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> ddaa52
-
breezefront / module-breeze-content-builder
2 commits 2-
head
breezefront / theme-frontend-breeze-enterprise-apollo
1 commit 1-
head
-
Firefox: Fixed horizontal scrollbar inside search slideout 613e59
-
breezefront / module-breeze-ai
18 commits 18-
head
-
fix(providers): agree on what a caller's system message means (#53)
* fix(providers): agree on what a caller's system message means
The three providers disagreed. OpenAI lets a caller-supplied system message
replace the built-in framing; Claude and Gemini filtered it out of the
conversation and then sent their own constant anyway, so the instruction was
read, dropped, and never reached the model. The same prompt therefore meant
different things depending on which model it happened to be assigned to — and
silently, because nothing errors when a system message is discarded.
Nothing sends one today, so this fixes no live failure. It removes a trap: the
first skill that reaches for its own framing would have got it on OpenAI and
lost it on the other two, and the symptom is bad output rather than an error.
`Model\Provider\SystemMessage` now resolves the framing for all three — the
caller's, or the built-in default when the caller supplied none. Where it goes
still differs per API: inline in `messages` for OpenAI, the top-level `system`
field for Claude, `system_instruction` for Gemini. More than one system message
is joined in order rather than reduced to the first, since Claude and Gemini
have a single field for it and dropping the rest would lose instructions.
The default string lived in three copies; `RequestBody::SYSTEM_MESSAGE` now
points at the shared constant so it keeps working for anything referencing it.
Claude and Gemini take a new constructor argument, so this needs
`setup:di:compile`.
./bin/test unit — OK (409 tests, 810 assertions)
phpcs --standard=Magento2 — 0 errors on all touched files
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(openai): resolve the framing through the shared rule, not a copy of it
The previous commit gave Claude and Gemini a shared SystemMessage but left
OpenAI deciding for itself in buildMessages(). Only the default string was
shared, so the two paths could still drift: several system messages were
joined on Claude and Gemini and left scattered through the conversation on
OpenAI, and any later change to resolve() would have reached two providers
out of three.
OpenAI now calls resolve() as well. The framing still travels as the first
entry of `messages` because that is what the endpoint takes; the caller's
system messages are lifted out of the conversation and folded into it, so
the instruction is sent once rather than twice.
resolve() also stops treating a blank system message as one supplied. An
empty string is not framing, and passing it through meant Gemini receiving
an empty `system_instruction` part — which it refuses — where before the
constant always went out.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 2c716b -
fix(openai): send the parameter set the model family accepts (#52)
* fix(openai): send the parameter set the model family accepts
Every request carried temperature, frequency_penalty and presence_penalty,
and max_tokens when configured. That is the GPT-4 parameter set. The GPT-5
family and the o-series refuse all four on the same endpoint: the two
penalties are rejected outright even at 0, temperature takes nothing but
its default, and the token cap was renamed to max_completion_tokens. A
request built the old way comes back 400, so those models did not degrade
on this provider — they failed every call.
Nobody hit it because the model dropdown stopped at gpt-4o-mini and
reaching a newer one meant typing it into the custom field. The client on
the translation project asked for gpt-5.6-terra, which is where this
starts to matter.
The body is now built by a small class of its own. The provider builds its
HTTP client inline and cannot be exercised without a network, which left
the part that has to be right per model family with no test at all; that
part is now pure and covered.
Family is decided by the model name, with a sampling extra-config key to
override it: an Azure deployment or a proxy can serve GPT-5 under a name
that says nothing, and prefix matching would send it what it refuses.
reasoning_effort and verbosity are passed through where they are
understood, so the effort knob is reachable without another release.
A caller-supplied system message now replaces the built-in one instead of
being appended after it — two system messages in one request left the
model to pick. No caller sends one yet.
The three GPT-5.6 tiers are in the model dropdown, so the client's model
is a selection rather than a hand-typed string.
phpcs --standard=Magento2 reports 0 errors on the touched files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(openai): gate the reasoning knobs per family and expose them in admin
The first pass treated GPT-5 and the o-series as one family. They are not:
`verbosity` shipped with GPT-5 and the o-series rejects it, and `o1-mini`
rejects `reasoning_effort` as well. Sending either to a model that refuses it
reproduces the 400 this branch set out to remove, so the knobs are now offered
per family while the token-cap rename and the dropped sampling parameters stay
common to both.
The knobs themselves had no way in. `sampling`, `reasoning_effort` and
`verbosity` were readable only by hand-editing the extra_config JSON, which
made the escape hatch for unnamed Azure and proxy deployments unreachable in
practice. All three are now fields on the model form, saved into and read back
out of extra_config alongside max_tokens.
Also:
- honour a configured `temperature` on the GPT-4 path instead of pinning 1.0,
matching what the Claude and Gemini providers already do
- read `sampling` without casting, so a non-string value in the JSON cannot
raise an array-to-string conversion
- note in the class docblock that an unrecognised model name falls back to the
GPT-4 parameter set, so the next family will need its prefix added here
./bin/test unit — OK (401 tests, 802 assertions)
phpcs --standard=Magento2 — 0 errors on all touched files
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> 37217f -
Merge pull request #51 from breezefront/fix/category-translate-source-language
fix(translate): name the source language when translating categories db336b -
docs(translate): tighten the source-store comment
The docblock read like a changelog entry for 1.0.11. Keep the reason the
source store is handed to the skill, drop the history around it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> f63c1b -
fix(translate): name the source language when translating categories
1.0.11 changed the translate prompt to name both languages ("translate
from Danish to German") because a weak model, told only the target, left
a good part of the text in the source language. The bulk consumer was
only half-converted: translateProducts() passes source_store_id, but
translateCategories() still calls the skill with the attribute and the
target store alone.
The source store was already used to load the category, so the values fed
to the model were right — only the prompt was missing the "from" half.
Category translations therefore kept running on the pre-1.0.11 prompt and
came back partly untranslated, the exact symptom 1.0.11 was released to
cure. Nothing surfaced it: the skill treats source_store_id as optional
and silently omits the clause when it is absent.
The unit test now asserts the whole options array rather than accepting
any call, so the next field added to it cannot go missing the same way.
phpcs --standard=Magento2 reports 0 errors on the touched files, the same
as before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6871f1 -
Merge pull request #50 from breezefront/fix/bulk-empty-field-panels-v2
fix(bulk): do not offer a task whose field panel has nothing in it acd541 -
fix(bulk): do not offer a task whose field panel has nothing in it
The wizard decides what step 2 may offer by asking whether a step 4 panel
exists for the pair. A panel could exist and still be empty: Generate
renders a row per attribute only when a prompt is bound to it, so with no
prompts configured the panel is a bare fieldset. The check said yes, the
operator picked Generate, and step 4 came up blank with a Run button that
never enabled — the dead end the panel check was meant to close, reached
from inside the panel instead of around it.
Prompts are now resolved once up front and an attribute without any is
dropped, which makes "the panel has fields" knowable before rendering.
A panel with no fields is not rendered, so the existing check reports the
truth for it. Translate gets the same treatment for symmetry, though its
field list is only empty on entity types nobody can select today.
That leaves step 2 able to come up with nothing at all in it, so it now
says why instead of showing an empty box.
phpcs --standard=Magento2 reports 0 errors on the touched file, the same
as before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 392330 -
Merge pull request #48 from breezefront/fix/bulk-save-skill-entity-validation
fix(bulk): refuse a skill the queue cannot run on the chosen entity type 24f2e1 -
Merge pull request #46 from breezefront/fix/bulk-wizard-skill-filter
fix(bulk): decide the offered tasks by the step 4 panel, not the skill's word 87738f -
fix(bulk): refuse a skill the queue cannot run on the chosen entity type
BulkConsumer::execute() routes on (skill, entity type) pairs and throws on
anything it has no handler for, but the scheduling controller accepted
whatever was posted. A request for a pair the consumer cannot route —
generate on cms_page, say, which the wizard hides but the form will still
carry — was answered with "has been scheduled", split into operations, and
failed one by one where only the bulk log would show it.
Save now checks the pair before it schedules anything, so the operator is
told no while still looking at the screen. The check runs before the entity
ids are resolved, which also spares a full-catalog id query for a request
that was never going to run.
QueueRoutes holds the pairs rather than inlining them, because the
consumer's routing table is the real authority and the two must not drift:
QueueRoutesTest reads the consumer's match arms and asserts they are
exactly the declared list.
It is deliberately not SkillSupport. That asks whether a skill can act on
an entity type at all and answers from getSupportedEntityTypes(); Generate
claims cms_page for the inline editor and is right to, but there is no
generate/cms_page route in the consumer. What a skill can do and what the
queue can carry out are separate facts, and only the second one decides
what may be scheduled.
370 unit tests pass. phpcs --standard=Magento2 reports 0 errors on the
touched files, and Save.php carries the same 10 warnings as before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 600476 -
Merge pull request #47 from breezefront/fix/cms-page-translate-scope
fix(translate): stop writing CMS page translations into a category 219f60 -
fix(translate): refuse an unsupported entity type in the skill itself
Narrowing getSupportedEntityTypes() to product and category told the truth
but gated nothing. Three paths still reached a CMS page:
- McpServer::executeSkill() passes entityType straight to GenerateService;
the schema enum is a hint to the client, not a check. translate +
cms_page + targetStore>0 went through Translate::execute(), which took
the attribute list from the input field pool — and the pool still knows
the cms_page fields — translated the text, saved nothing, and reported
success. A silent no-op instead of an error.
- The CMS page form kept its "AI Translate" buttons. Block\Adminhtml\Js
hands translate.js every input field of the entity type, and the form
has no store switcher, so currentStore is always 0: every click ran
translateForAllStores() and died on the new save guard.
- That death cost one AI request. translateForAllStores() calls the skill
first and saves after, so the provider was paid before anything refused
the entity.
Translate::execute() now asserts the entity type against its own
getSupportedEntityTypes() right after resolving it — before the provider
call, so a refused entity costs nothing, and for every caller at once.
saveAttributeForStore() keeps its throw as the last line of defence.
SkillSupport answers "can this skill act on this entity type" for UI code;
Js publishes skills.translate and translate.js renders no buttons when it
is false. Generate still gets the full field list.
Js gained a constructor argument — setup:di:compile.
364 unit tests pass. phpcs --standard=Magento2 reports no errors on the
touched files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 551992 -
refactor(bulk): give the step 4 panel lookup one home
The panel query behind the wizard's "can this task run here" check was
written out three times over. wizardCanRun() and hasFieldSelection() ran
byte-identical selectors, each with its own empty-argument guard, so a
change to how a panel is addressed had to be made in more than one place
to hold.
findPanel() is now that one place, and both callers read as what they
ask: does a panel exist, and does the panel that exists have a selection.
The Build Landing Page panel also rendered unconditionally, though step 1
offers CMS pages only when the Content Builder is installed. Without it
the panel was unreachable markup in every admin; it now follows the same
condition as the content type it serves.
No behaviour change. 358 unit tests pass; phpcs --standard=Magento2
reports the same 0 errors as before on the touched file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6d13ac -
fix(translate): stop writing CMS page translations into a category
translateForAllStores() routes every non-product entity through
saveAttributeForStore()'s else branch, which loads a *category* by the entity's
id. For a CMS page that id is a page_id, so translating page 8 for all store
views wrote its translated meta_title onto category 8 — on this install, "New
Luma Yoga Collection". A silent write into an unrelated record, no error, no
trace.
Extending the branch is not the fix either: cms_page has nowhere to put a
per-store-view value. The table holds one row per page — title, meta and content
are not store-scoped — and the store assignment lives in cms_page_store. A
translated page in Magento is a separate page. So Translate stops claiming
cms_page, which also narrows the MCP tool's entityType enum, and
saveAttributeForStore() raises on anything it does not explicitly handle instead
of guessing: entity ids collide freely across tables, and guessing corrupts.
Generate keeps cms_page — that path is real. GenerateService::loadEntity()
already resolves pages through PageRepository, so the AI button works on the SEO
fields of the CMS page form. content_heading joins them: plain text, same
mechanics as product-name, and the one obvious gap. title and identifier are
left out on purpose (identifier drags url rewrites behind it), and content
belongs to the Build Landing Page skill, which writes the whole document.
Bulk is unaffected: BulkConsumer never handled generate/cms_page and still does
not, so the wizard offers Build Landing Page alone for CMS pages.
Verified against the compiled DI:
cms_page fields content_heading, meta_title, meta_keywords, meta_description
generate [product, category, cms_page]
translate [product, category]
build_page [cms_page]
360 unit tests pass, including one that pins the corruption: translating a CMS
page for all store views now raises and never touches the category repository.
phpcs --standard=Magento2 reports no errors on the touched files. afe08f -
fix(bulk): decide the offered tasks by the step 4 panel, not the skill's word
Picking CMS Pages still listed Generate Content and Translate: both declare
cms_page in getSupportedEntityTypes() for the inline editor, so the filter added
in the previous commit let them through. Choosing either landed on an empty step
4 with no step 5 and no Run button — the exact dead end this branch set out to
remove, just reached by another route.
A skill's own entity list says nothing about whether this screen can drive it.
The wizard now asks the only thing that matters: does a step 4 panel exist for
this (content type, task) pair. That is the condition for not dead-ending, and it
stays true as panels are added or removed.
getSkillEntityTypes() existed only to feed the old check and is dropped with it.
Verified in the admin: CMS Pages now offers Build Landing Page alone, step 3 is
skipped, step 4 reads "Describe the page", step 5 lists the 84 pages and Run
schedules "AI Build Landing Page for 1 CMS page". 92afa6 -
Merge pull request #45 from breezefront/feat/bulk-cms-landing-page
feat(bulk): build landing pages from the AI Bulk Action wizard eeabe7 -
fix(bulk): file the generated page draft under the scheduling admin
BCB drafts are per user: Content/Load reads getDraft($pageId, 0, $currentUserId)
and the collection filters on equality, so a draft written for anyone else is
invisible in the editor. bcb_page_content.user_id is also a foreign key on
admin_user, so the placeholder 0 the consumer was passing failed the insert
outright — every build_page operation died with an integrity violation and no
draft was ever stored. The unit tests missed it because DraftWriter is mocked.
The scheduling admin's id now travels in the payload and DraftWriter requires it,
rejecting 0 before touching the repository. Save refuses to schedule a page build
when the current user cannot be identified, rather than queueing work that cannot
land.
Alongside that:
- CMS Pages is only offered when the Content Builder is installed. Without it
nothing can store the document, so the wizard used to run the whole batch
through the AI only to fail on save. Step 1, the grids and the LISTING map are
all derived from ViewData::getEntityTypes() now.
- Step 4's heading follows the picked task: "Describe the page" for Build Landing
Page instead of the field-list copy, which still said "Select attributes such
as name, description, meta title".
- Operator messages use real names: "AI Build Landing Page for 3 CMS pages"
rather than "AI Build_page for 3 cms_page(s)".
- Step 4 warns that the run replaces the current draft on every selected page.
- Dropped the unused is_active column from the CMS page grid's select.
Tests: DraftWriterTest covers both guards and the write, SaveTest covers the
payload and the messages, BulkConsumerTest asserts the user id reaches the
writer and that a payload without one fails the operation. 359 pass; phpcs
--standard=Magento2 reports no errors. e8f294 -
feat(bulk): build landing pages from the AI Bulk Action wizard
Step 2 listed every registered skill, so Build Landing Page was offered for
products and categories. It declares cms_page and no attributes, leaving step 4
empty and step 5 unreachable — the wizard dead-ended with no error.
Tasks are now filtered by getSupportedEntityTypes() against the content type
picked in step 1, and CMS Pages joins that step so the skill has a real path:
a brief instead of the per-attribute field list, a CMS page grid, and no store
view step (Content Builder documents are store-agnostic).
The generated document is stored as the page's Content Builder draft. The BCB
page flag is left alone: turning it on with nothing published renders a blank
storefront page, so enabling the editor and publishing stay manual. The BCB
dependency stays soft — the repository is resolved by name behind
interface_exists(), like the component catalog reader.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> afe7f0
-
breezefront / module-breeze-integrations
2 commits 2-
head
breezefront / module-breeze
3 commits 3-
head
* Repositories are sorted to display abandoned activity on the top of the list