Recent Activities
This page shows what are we working on.
-
1.1.0
-
1.0.0
-
1.1.0
-
head
-
Merge pull request #122 from breezefront/fix/98-enabled-flag-inert
fix(admin): make Enabled = No shut the module's admin surface (#98) 369325 -
fix(admin): make Enabled = No shut the module's admin surface (#98)
The flag suppressed what the module injects into other people's forms and
nothing else. Its own menu was declared unconditionally and its controllers gated
on ACL alone, so a merchant who switched it off still saw Swissup > Breeze AI and
could open and run the bulk wizard — and a run already in the queue kept writing
generated text over the catalogue.
Three parts. dependsOnConfig on the menu branch root, which is enough because
Magento's menu filter drops a disabled item without descending into its children.
An observer on the module's one admin route rather than a check in twenty
controllers, so a controller added later cannot forget it; it sets
FLAG_NO_DISPATCH — without which Save and Cancel write anyway — and redirects to
the section holding the switch, because a page that only vanishes is a support
call. And the consumer refuses a queued operation, closing it as failed with the
reason rather than returning, since an operation left open is the run that hangs
for ever.
AdminSurfaceTest pins the three declarations to each other: one admin route, an
observer on that route's event, and a menu branch that names the flag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 705e82
-
-
1.7.0
-
chore(release): 1.7.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 37dee3 -
Merge pull request #120 from breezefront/feat/116-bulk-cancel
feat(bulk): cancel a run, and close operations an external kill left open (#116) 2bba39 -
fix(bulk): say what a cancel could not reach (#116)
finish() inserts a row with a null operation_key for a message an earlier release
queued, so the first such operation to end puts its run in the AI Jobs grid — and
if it failed, offers Cancel. Cancelling rejected that one row, MAX(status) read
the run as cancelled, and the operations that were never recorded went on running.
Canceller::countUntracked() is operation_count minus the rows this module has, and
both cancel paths now warn with it. The cancel is not blocked: rejecting the rows
it can see is still worth doing, and a recording that failed part-way is a run
that needs cancelling more than most.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 7ee734 -
fix(bulk): say what a cancel cannot stop in the plural (#116)
A topic can be drained by several consumer processes at once, so more than one
operation of a run can be in flight. "One already running... the rest will not
be started" is wrong on any installation running more than one consumer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 132e44 -
fix(bulk): stop the operation results claiming more than they know (#116)
The cancel message named the admin on a row breezeai:bulk:cancel also writes;
the sweep's promised nothing more would be written, which a redelivery or a slow
consumer can contradict; and both counts were reported as work stopped when they
include operations that will finish anyway.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 82ebeb -
fix(backup): keep operation key zero as an id (#116)
Read with `?:`, which is false for 0. Harmless while operations carried no key,
but they are keyed within their bulk now and the first chunk is key 0 — so every
capture and usage-log row of the first chunk, and of any run that fits in one,
lost its operation id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> e620ed -
fix(bulk): lock the bulk row, not a gap in the operations (#116)
Gap locks are purely inhibitive and do not exclude each other, so the recording
and an overtaking finish() both acquired the FOR UPDATE on the empty bulk_uuid
range and their inserts then deadlocked. Measured: ERROR 1213 for the operation
range, a proper wait for the bulk row, which is a guaranteed primary-key record.
A finish() chosen as the victim throws after the AI work has run and out of
process(), so the message is never acknowledged and the broker hands the
operation back to be paid for a second time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 194467 -
refactor(bulk): append the operation recorder to the schedule constructors (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> d70c5c -
docs(bulk): name Magento's own orphan cron and what 0 leaves to it (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1e5b27 -
fix(bulk): cancel the operations a sweep had already closed (#116)
A failed operation is not finished business under AMQP: its message was never
acknowledged, the broker hands it back, and OperationRecord::start() reopens the
row. Cancel only touched open rows, so a run whose operations the sweep had
closed — the ordinary state of an interrupted run by the time anyone presses
Cancel — could not be stopped, and the grid hid the control besides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 1db966 -
test(consumer): construct the consumer with its OperationRecord (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6abe58 -
Merge branch 'master' into feat/116-bulk-cancel 38f0f9
-
test(bulk): cover the CLI commands and the job grid's ACL gates (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 0e363a -
fix(bulk): compare-and-set the claim, serialise both insert paths (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 79bc47 -
style(test): give SelectProbe its own file, drop the trait constant (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 625310 -
fix(bulk): refuse a redelivered completed operation, bound the sweep query (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> ccb719 -
fix(bulk): close the schedule and sweep races the review found (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> fc336c -
feat(bulk): cancel a run, and close operations an external kill left open (#116)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 37f906 -
Merge pull request #118 from breezefront/fix/114-provider-timeout
fix(providers): give every text call an HTTP timeout (#114) 9bc731 -
refactor(providers): make the request timeout per model (#114)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 4de876 -
Merge pull request #117 from breezefront/fix/115-consumer-throwable
Reviewed and re-ran the unit suite from the installed copy: OK (1358 tests, 2949 assertions), 9 over the 1349 baseline on master. Reverting the catch to \Exception errors the 6 new tests with the uncaught TypeError, so they do gate the fix. phpcs --standard=Magento2: 0 errors on all three files.
Two judgement calls, both kept as the author had them:
- The generic \Exception branch was removed rather than kept above the new one. \Throwable is last, so it catches everything \Exception did with a byte-identical body — a kept branch would be dead weight, not a safety net. The three specific branches above it still own their messages and retriability.
- $errorCode = $e->getCode() now applies to \Error too, storing error_code = 0 where getCode() is 0. That is not new: a plain \Exception also reports 0, so the branch this replaces already stored 0 for most of what it caught. Making \Error alone store null would split the behaviour for no gain.
Trimmed before merge: the comment above the new catch went from five lines to two, keeping why \Throwable and dropping the restatement of what the \Exception branch used to do. 2829cf -
style(consumer): trim the \Throwable catch comment to the decision (#115) 8f1d80
-
fix(providers): give every text call an HTTP timeout (#114)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 73459b -
fix(consumer): end an operation with a status on any \Throwable (#115)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> ec278e
-
-
head
-
Merge pull request #38 from breezefront/feat/108-newsletter-and-missing-components
feat(components): inline newsletter layout (#108) 57dafb -
fix(components): newsletter heading follows an inherited text colour (#108)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> c56d7d -
fix(a11y): real heading elements and decorative icons (#41)
Newsletter rendered its section heading as <div class="...__heading h2">:
invisible to assistive technology and absent from the document outline,
even though it is the heading of a page band. It now emits a real h1-h6
element chosen by a new heading_type prop (default h2), matching what
text_block already does. Same fix for the featured_product name, which
was also a styled div.
Breeze styles the tag and the class off a single selector list -
`h2, .h2 { ... }` in theme-frontend-breeze-blank/web/css/base/
_typography.less - so under a Breeze theme, dropping the class for the
tag is visually neutral. Under a non-Breeze theme .h2 is not styled at
all, so those headings were rendering as plain body text and now pick up
the theme's own h2 size: a fix rather than a regression, but not a no-op.
Either way it changes the markup of existing pages, which is why this is
split out of #38.
text_block borrowed the heading text for its icon alt and wrapped inline
SVG in a plain div, so a screen reader announced the heading twice, or
three times when the SVG carried its own <title>. The icon is decorative
in every design that uses it: alt="" on the image branch, aria-hidden on
the SVG wrapper. A case that needs a real name should get an explicit
icon_alt prop rather than reusing the heading.
The admin H1 guard only fired on banner's headline_type, so raising a
text_block - and now a newsletter - to H1 skipped the warning. It now
watches heading_type too; _countH1 already counted both.
Also corrects the selector these three comments cite: the rule is
`h1, .h1` in the Breeze theme, not `:where(h1), .h1` in the module.
product_grid was checked and already emits a real h2. Accordion item
titles remain divs wrapping an <a> trigger; the heading+button pattern
there is a larger change and stays open.
Refs #41 53edaa -
fix(components): collage layout for flat-colour CTA, decorative icon a11y (#108)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 6e2743 -
feat(components): inline newsletter layout (#108)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> 85ed20 -
Changes for header/footer page: publish spinner, layout picker with images 25b2ae
-
-
1.2.2
-
1.0.4
-
Version 1.0.4 2f49ee
-
Problem with 404 page #3 c6c6ff
-
Improve blocks benefits, contact. b75121
-
Improve mobile footer #8 498bd4
-
Improve mobile logos #9 cbaa8d
-
Improve mobile footer #8 c00893
-
Improve mobile category #11 905ae1
-
Improve spacing in product card #2 3e0594
-
mprove mobile category #11 f25a29
-
style: improve navigation filter 5a8bfd
-
style: customize product list item template 8e256e
-
fix: Avoid using self-closing tag e0fa93
-
-
1.0.3
-
1.1.5
-
1.1.4
-
1.2.3
-
1.2.2
-
head
-
Product main info ... in progress bf5c3f
-
Enhance product quantity input with increment/decrement buttons and update styles for better usability 72192e
-
Update product page medium image dimensions to 590x590 012c72
-
Fix minicart styles: set empty counter opacity to 0 79fcd1
-
Increased h2 bottom margin dff6ea
-
Product page action buttons. d008f0
-
Updated footer content links and section titles for improved clarity and user experience 241039
-
BCB homepage: fixed wrong image name b1a4f4
-
Rating stars styles in product listing f17a0f
-
BCB homepage: updated full width banner and sections spacings 2076c7
-
Fixed sort order of product details inside grid listing 9ad360
-
BCB homepage: updated Videos and full width banner sections 072733
-
Product page title font-size. d0987c
-
Footer. Minor fix for ring reval animation. Restore missing divider in footer. (#3) 54cdc4
-
Footer ring reveal animation added (#3) 4a8be8
-
BCB homepage: added animated badges to top banners grid 197e37
-
BCB homepage: added rotation animation for Full Width Banner 1420f9
-
BCB homepage: top banner updated a53bd5
-
Update favicon color 5ea666
-
Update favicon 18d8dc
-
Slightly update footer background. 5fcbfc
-
Footer content update. Add contact block (#3) dea42e
-
Fixed missing minicart overlay on first open (#2) 54feaa
-
BCB homepage: updated Browse categories block dbfa89
-
Breadcrumbs 196469
-
Done with search (#2) d8e0f3
-
Enhance minisearch functionality with slideout behavior and styling adjustments without Ajaxsearch (#2) 8c4a9d
-
Minisearch styles when ajaxsearch disabled... in progress (#2) 259fac
-
Remove unused styles from viewcart action and update empty subtitle styles in minicart (#2) dba7a5
-
Minicart complete (#2) 56efd2
-
Search on desktop (#2) 9c587e
-
BCB homepage: added videos section 7ee864
-
Minicart inprogress (only buttons to adjust)... (#2) c1be91
-
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
-
-
head
-
1.2.0
-
head
-
Merge pull request #34 from breezefront/fix/default-scope-theme-resolution
fix(theme-resolver): resolve Default scope theme from store views 900bf4 -
test(theme-resolver): cover inactive default store view and all-inactive website
Third review round over c0ae76c found no defects in the fallback ordering
itself, but two branches of it were untested: the scope's default store view
being switched off (the resolver must move on to the first active view, as the
scope selector does) and a website whose store views are all switched off
(nothing to preview, nothing to fall back to). 1b3a31 -
fix(theme-resolver): follow the previewed store view in the theme fallback
Review of the previous commit (mine and Copilot's) landed on the same problem:
the fallback took the first themed store view out of `getStores()`, in store-id
order, including disabled ones. The scope selector previews a specific store —
the default website's default store for Default scope, the website's own default
store for a website scope (StoreDataProvider::getDefaultStoreId()) — so on a
multi-theme setup the editor could load and save settings for one theme while
the iframe rendered another, with no visible effect from a publish.
The candidate list now mirrors that order: the scope's default store view first,
then its other store views, and inactive views are skipped entirely — their
theme is rendered nowhere. Walking past the previewed store view stays as a last
resort, matching StoreDataProvider's own fall back to the first active store; it
is reachable only when the previewed view has no theme in any scope, where
Magento renders its built-in default and there is no assignment to follow.
The blanket `catch (\Exception)` also reported infrastructure faults as
"No theme is assigned … Assign a theme in Content > Design > Configuration",
losing the real cause. A missing website/group/store stays silent (expected
input); anything else is logged as a warning with the exception attached.
Verified on the local stack with the default-scope row deleted: Default scope
resolves to theme 25 when the previewed store view (store 1) has it, instead of
theme 17 from the lower-id store 3 that the previous commit returned; website
scope resolves through its own default store the same way. Tests: PHPUnit 905. c0ae76 -
Version 1.0.16 1bcff1
-
fix(theme-resolver): resolve Default scope theme from store views
The Theme Editor failed with "Unable to determine theme for scope default /
scopeId 0" on installs where design/theme/theme_id was never saved for the
Default Config scope — themes assigned per store view only. Website and store
scopes read that value through ScopeConfig and inherit it; the default scope has
nothing above it, so the lookup came back empty and the resolver threw. The
admin toolbar already fell back to DesignInterface, so the editor loaded and
only the config query failed, which is what the client saw.
getThemeIdByScope now falls back to a store view's theme when the requested
scope has none of its own: the default store view first (then any other one)
for the default scope, and the website's own store views for a website scope.
Store scope keeps no fallback — it inherits from default already, so an empty
value there means nothing is assigned anywhere.
When nothing can be resolved the message now names the cause and the fix
instead of the scope tuple: which scope has no theme, that no store view has
one either, and that a theme has to be assigned in Content > Design >
Configuration followed by a cache flush.
Not a regression from 1.0.15 — that release only touched GraphQL auth headers.
The resolver has behaved this way since multi-scope support was added (5d43943).
Verified against the local stack with the default-scope row deleted: the old
code reproduced the client's exact error, the new code returns a valid config
for both default and website scope. Tests: PHPUnit 904. c085d4
-
-
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
-