Untagged Commits
This page helps make in-time deliveries for all new goodies.
breezefront / theme-frontend-breeze-blank
2 commits 2-
head
breezefront / module-breeze-algoliasearch
1 commit 1-
head
-
Update integration 8e9569
-
breezefront / module-breeze-magento-payment-services
1 commit 1-
head
-
Added missing less variable `checkout-shipping-address__max-width` 0c0109
-
breezefront / module-breeze
14 commits 14-
head
-
Fixed `undefined` menuSlideout when using third-party menu's 6ebe7a
-
Reusable slideout widget 127b7c
-
Wrap tab switch into startViewTransition to allow view-transitions 9018b1
-
Allow configuring scroll reveal onReveal options from block d7812c
-
Breadcrumb min-height: don't override theme style 8f3084
-
Improve stability of prev commit 7750f3
-
Minisearch: fixed non focusable input if it's hidden initially 2fde2a
-
Fixed incorrect magnifier position when gap in rem's 9d36ab
-
Submit form when clicking the search label and hide it if input is empty bece72
-
BreezeTheme 3.0 43137d
-
RangeSlider: use hex colors and color-mix to add transparency 015ee2
-
Fixed missing provider, proper element binding when using `uiLayout` c7f760
-
Shorten line length 3501ae
-
Better compatibility with complex ui components (Amasty_MegaMenu) 7fe7ae
-
breezefront / module-breeze-theme-editor
337 commits 337-
head
-
docs: mark п.3.3 + п.3.4 god-class decompositions as complete in PLAN.md (20/107) 448090
-
refactor(publication-selector): decompose 1019-line god-widget into thin orchestrator + 2 helpers (п.3.4)
publication-selector.js: 1019 → 512 lines (thin orchestrator)
+ action-executor.js: 494 lines (publish, rollback, discard, delete mutations)
+ css-state-restorer.js: 255 lines (CSS mode init, restore, switch, load)
Tests: +action-executor-test.js, +css-state-restorer-test.js (592 JS / 684 PHP) d4700e -
fix(section-renderer): require palette/font-palette renderers to guarantee widget registration
The paletteSection and fontPaletteSection jQuery widgets are defined in
sections/palette-section-renderer.js and sections/font-palette-section-renderer.js
but neither file was ever require()d before section-renderer.js called them.
This caused silent failure: palette/font-palette sections never initialized.
The typeof guards added in 709c593 masked the symptom but did not fix the root
cause. This commit adds both files as AMD side-effect dependencies of
section-renderer.js, then removes the now-unnecessary typeof guards. cb7c14 -
Fix void tag closing slash in loading-test.js fe5940
-
docs: mark п.3.3 settings-editor decomposition as complete (19/107) 08a861
-
fix(section-renderer): guard widget calls when jQuery plugin not yet registered
Add typeof checks before calling .paletteSection(), .fontPaletteSection(),
and .presetSelector() to prevent TypeError when the widget module has not
been loaded/registered yet (e.g. in the test environment). 709c59 -
refactor(settings-editor): decompose 1511-line god-class into 5 focused helpers (п.3.3)
- config-loader.js — GraphQL load / loadFromPublication + FontPalette seeding
- error-presenter.js — error panel, toast, parseErrorData, getFriendlyMessage
- field-editability.js — enable/disable fields, read-only click-guard
- search-handler.js — search filter, clear, debounce
- section-renderer.js — accordion HTML, palette/font-palette/preset init, font preload
settings-editor.js rewritten as thin orchestrator (570 lines, all logic delegated).
All 684 PHP unit tests pass. b9dad4 -
docs(refactoring): add setTimeout audit section (8.1–8.13), update stats to 18/107
- New category 8: setTimeout audit — 13 tasks classified into 7 types
(A retry polling, B CSS anim sync, C next-tick, D flag cleanup,
E outside-click guard, F debounce, G intentional delay, H race condition)
- Update table of contents, stats table (94→107 total), header
- Sync DASHBOARD.md: 18/107, new row in category table, updated metrics 406965 -
fix(test): fix toolbar-toggle _restoreState() test timeout
makeEnv() now accepts an optional storedState param so state is set
BEFORE widget init, allowing _create()/_restoreState() to pick it up.
Removed the explicit _restoreState() re-call that was causing the
timeout by triggering a second async _hideToolbar() after env setup. 5d53b3 -
test(js): add 7 unit test suites for utils/ui and toolbar modules (п.3.3 pre-work)
Covers error-handler, permissions, loading, cookie-manager, device-switcher,
toolbar-toggle and highlight-toggle — 83 new in-browser JS tests total.
Also registers all suites in TestRunner.php and syncs docs counters to 18/94. 5be7e1 -
refactor(toolbar): decompose AdminToolbar into 5 focused helper ViewModels (п. 3.2)
- Drop dead Json $jsonSerializer dep (was injected but never used)
- Extract ToolbarScopeProvider: scope/store resolution (getScope, getScopeId, getStoreId, getStoreCode)
- Extract ToolbarAuthProvider: auth + user identity (canShow, getAdminUsername, getUserId, getToken)
- Extract ToolbarPermissionsProvider: ACL checks (canEdit, canPublish, getPermissions)
- Extract ToolbarUrlProvider: URL building (getAdminUrl, getGraphqlEndpoint)
- Extract ToolbarThemeProvider: theme ID resolution delegating to ToolbarScopeProvider
- AdminToolbar becomes thin orchestrator: 8-arg constructor, all public methods kept via delegation proxies
- Update AdminToolbarTest setUp() to use real ToolbarScopeProvider with BackendSession mock
- Add 5 new test files in Test/Unit/ViewModel/Toolbar/ covering all helper ViewModels
- 684/684 tests pass 98545c -
refactor(css): decompose CssGenerator into CssValueFormatter, CssVariableBuilder, CssFontImportBuilder (п. 3.1)
- Extract CssValueFormatter: formats individual values (color, font, spacing, repeater, escape)
- Extract CssVariableBuilder: buildFieldMap, buildSelectorBlocks, buildPaletteVarsToEmit
- Extract CssFontImportBuilder: collects @import URLs for web fonts
- CssGenerator reduced to thin orchestrator with 5-arg constructor
- Fix CssGeneratorTest setUp() to use real helper instances instead of mocking ColorFormatResolver
- Add 95 new unit tests: CssValueFormatterTest, CssVariableBuilderTest, CssFontImportBuilderTest 389b3b -
docs(dashboard): sync with PLAN.md — 16/94 completed, Кроки 1-3 done 366c86
-
feat(publication): suggest versioned title when creating new publication
Pre-fill the publish prompt with a suggested name derived from the most
recent publication title. If the previous title ends with _vN the version
is incremented; otherwise _v1 is appended. When no previous publication
exists the prompt opens blank (existing behaviour). 8f3a34 -
docs(plan): mark Крок 3 complete (пп. 4.1, 4.5, 4.7, 4.2 DONE), update counters 16/94 915a17
-
refactor(duplication): unify URL building via buildUrl() in PageUrlProvider (п. 4.2) 4e1e1d
-
refactor(duplication): extract hex8ToRgba() into ColorConverter and ColorUtils (п. 4.7) a3ad5d
-
refactor(duplication): extract getDraftUserId() helpers into AbstractMutationResolver (п. 4.5) ee89eb
-
refactor(duplication): move extractLabels() into PublicationDataTrait (п. 4.1) d8483e
-
docs(refactoring): mark п. 5.5 as N/A — status-indicator.js was deleted in 88b6aa6
Progress: 12/94 completed 81d617 -
docs(refactoring): mark п. 2.1 as done — AdminToolbar dead deps removed
Progress: 11/94 completed a33ed6 -
refactor(dead-code): remove unused DI deps and stub methods from AdminToolbar
- Remove PublicationRepositoryInterface, SearchCriteriaBuilder, SortOrder imports
- Remove $publicationRepository and $searchCriteriaBuilder property declarations
- Remove unused constructor params and assignments
- Remove stub methods getInitialPublications(), getInitialPublicationStatus(),
getCurrentPublicationId() — they always returned fixed values ([], 'DRAFT', null)
- Inline their constant values directly in getToolbarConfig()
- Update AdminToolbar class docblock (drop stale 'Publications list via PublicationRepository')
- Update AdminToolbarTest: remove PublicationRepositoryInterface and
SearchCriteriaBuilder mocks from setUp()
Closes п. 2.1 of refactoring plan. 6955d0 -
fix(css): fix responsive selectors for publication-selector (toolbar-publication-selector → bte-publication-selector) b978bf
-
docs(refactoring): mark 2.16, 2.17, 2.18 as done — 9/94 total 4c46de
-
docs(refactoring): mark step 1 complete — all 6 critical bugs fixed (1.3, 1.4, 5.1) 88b6aa
-
fix: remove dead SetThemePreviewCookie observer
The observer read '___store' from the request to set 'store' and
'preview_theme' cookies for the editor iframe. Investigation showed
the logic is fully covered by two existing mechanisms:
1. Magento core StoreResolver already reads '___store' URL param and
switches the store view natively — no cookie needed for that request.
2. JS cookie-manager (page-selector.js:214, scope-selector.js:340)
calls setNavigationCookies() / setStoreCookie() via document.cookie
before every iframe navigation, covering subsequent requests.
The observer was also registered on the wrong event
(admin-only 'controller_action_predispatch_breeze_editor_editor_index')
so '___store' was never present there — making it completely dead.
Removed: Observer/SetThemePreviewCookie.php
Removed: etc/adminhtml/events.xml (only contained this observer) 428725 -
fix: remove stray space in urn scheme in etc/frontend/di.xml
'urn: magento:...' is an invalid URI — the space after the colon breaks
strict XML schema validation. Corrected to 'urn:magento:...'. 1e8d8f -
fix: move SetThemePreviewCookie observer from adminhtml to frontend context
The observer was registered on the admin-only event
'controller_action_predispatch_breeze_editor_editor_index', but it reads
the '___store' URL parameter — a Magento frontend store-switcher convention
that is never present in admin requests. Cookies for the iframe must be set
on the frontend side when the iframe page itself is loaded.
Moved the observer to etc/frontend/events.xml on the generic
'controller_action_predispatch' event. The existing guard
(early return when '___store' is absent) ensures zero overhead on normal
frontend requests. 619354 -
fix: resolve PUBLISHED statusId via StatusProvider instead of magic integer
SavePaletteValue::resolve() was calling setStatusId(1) hardcoding the DB
row ID for the PUBLISHED status. If the ID ever changes the record would
be saved with a wrong status silently. Now delegates to
StatusProvider::getStatusId('PUBLISHED') — consistent with the rest of
the codebase. Updated test to inject a StatusProvider mock. 6e59fd -
docs(refactoring): mark 3 critical bugs as done (1.1, 1.2, 2.21) 4610ac
-
fix: remove duplicate getDirtyCount() definition in palette-manager.js
In a JS object literal the second definition silently overwrites the first.
Both were identical, but the dead first definition was a latent bug — any
future divergence between the two would be invisible. Removed the first
occurrence (was at line 309), keeping the canonical one at the end of
the object with its full JSDoc. 156025 -
fix: validateValues() now accepts ValueInterface[] instead of array[]
ImportExportService::import() was passing ValueInterface[] objects to
validateValues(), but the method used array access ($value['sectionCode'])
instead of getter methods. Validation was silently broken for all imports.
Fixed by switching to getSectionCode()/getSettingCode()/getValue() and
updated the test to use ValueInterface mocks instead of plain arrays,
ensuring the contract between caller and callee is actually tested. 54d007 -
fix: align ImportSettings resolver keys with ImportExportService return value
ImportExportService::import() returns 'importedCount'/'skippedCount' but
the resolver was reading 'imported'/'skipped', causing both GraphQL fields
to always return null. Fixed resolver key access and corrected the test
mock that was masking this mismatch. 0e4e4c -
refactor(storage): migrate remaining direct localStorage calls to StorageHelper
- toolbar-toggle.js: replace direct localStorage with StorageHelper.getGlobalItem/
setGlobalItem('admin_toolbar_visible'); remove storageKey option
- logger.js: rename key 'bte-log-level' -> 'bte_log_level' (aligns with legacy
flat-key convention; avoids circular dep with storage-helper.js)
- storage-helper.js: extend getGlobalItem/removeGlobalItem to also migrate/clean
legacy hyphen-prefixed keys (e.g. bte-admin-toolbar-visible, bte-log-level)
- storage-helper-test.js: fix pre-existing KEY_SECTIONS bug; add GROUP 3 tests
for getGlobalItem/setGlobalItem/removeGlobalItem including hyphen-key migration
- url-navigation-persistence-test.js: add clearScope() cleanup after Tests 2, 5,
21, 26 to prevent cross-run localStorage pollution
- panel-integration-test.js: remove unreliable is(':visible') assertions; increase
open-wait 50->100ms and close-wait 350->450ms for stable animation timing 562b3e -
docs(refactoring): add code quality audit plan with 94 refactoring tasks across 7 categories 91044a
-
refactor(storage-helper): consolidate localStorage into single 'bte' key with nested structure
Replace flat bte_{storeId}_{themeId}_{key} keys with a single JSON object:
bte = { global: { admin_token }, 1: { 5: { current_url, ... } }, ... }
- Add getGlobalItem/setGlobalItem/removeGlobalItem for cross-scope values
- Auto-migrate legacy flat keys on first read, then remove them
- Move admin_token from bte_admin_token to bte.global.admin_token
- Add StorageHelper dependency to graphql/client.js
- Update all tests to use new structure (readRaw/clearScope helpers) 2b3ba0 -
test: add sync-fields-from-changes tests for Issues 018 and 019
12 tests across 3 layers:
- Layer 1 (pure jQuery, always runs): color preview dot updated, text input
updated, null-displayValue guard, regression for old attr('type') path
- Layer 2 (PanelState isolated): getChangesCount() > 0 after setValue(),
var(--x) normalization, count equals number of synced dirty fields
- Layer 3 (integration, skips outside DRAFT): syncFieldsFromChanges() e2e
for both color preview (Bug A) and getChangesCount (Bug B), plus text
field, checkbox, and empty-changes-map edge cases
Also updates discard-published-preview-test.js: getCss() call signature
updated to pass scope type as first string argument instead of storeId int. 387927 -
fix(settings-editor, css-preview-manager): restore Save(N) count after F5 (Issue 019)
Two-part fix for 'Save (0)' button and disabled Reset after page reload in DRAFT mode.
Part 1 — timing (settings-editor.js): _initPreview() now stores the Promise
returned by CssPreviewManager.init() as this._previewReady. _loadConfig()
chains syncFieldsFromChanges() on _previewReady instead of a fixed setTimeout,
guaranteeing sync runs after localStorage changes are loaded. After sync,
_updateChangesCount() is called immediately — PanelState is already updated
(see Part 2), so the button shows the correct 'Save (N)' count.
Part 2 — async require (css-preview-manager.js): the PanelState.setValue()
call inside syncFieldsFromChanges() was wrapped in an async require([...], cb).
This meant setValue() ran in a future microtask, so getChangesCount() still
returned 0 when _updateChangesCount() fired. Replaced with synchronous
require() inside try/catch — safe at call time because all modules are
already in the RequireJS cache when syncFieldsFromChanges() is invoked.
Also: CssPreviewManager.init() refactored to return a Promise that resolves
once #bte-theme-css-variables is present in the iframe (reliable 'frontend
ready' signal), eliminating the outer 500 ms setTimeout in _initPreview(). 6dab43 -
fix(css-preview-manager): update .bte-color-preview dot on F5 reload (Issue 018)
syncFieldsFromChanges() used $field.attr('type') to detect color fields,
but $field[0] is a .bte-color-trigger div — it has no type attribute, so
the check always returned undefined and fell through to the else branch,
which called $field.val() on the div and never touched .bte-color-preview.
Fix: use fieldType ($field.data('type')) which correctly reads data-type='color'
from the div, then update .bte-color-input via val() and .bte-color-preview
via css('background-color', ...) directly.
Also: resolve palette var(--color-x) references to their HEX display value
before updating the color input, and guard the update block with
if (displayValue !== null) so a failed palette resolve skips the DOM update
rather than setting the input to null. 6b186d -
Increase gap between scope name and gear icon to 8px 80cbcd
-
refactor(publication-selector): rename 'changes' to 'modifications' in UI strings
'changesCount' refers to saved-but-unpublished fields, not unsaved UI edits.
Use 'modifications' consistently across badge, meta, publish/discard/reset buttons
and publication item date line to avoid confusion with unsaved panel changes. 592f92 -
fix(settings-editor): allow toggling palette section header in read-only mode
- Add .bte-palette-header to read-only click guard whitelist so expanding/
collapsing Color Palettes accordion no longer prompts to switch to Draft
feat(publication-selector): show changes count in publication item meta
- Display '· N changes' inline in item-meta date line for each publication
that has changesCount > 0 (data already available from GraphQL) 057902 -
fix(setup): drop legacy store FK before ALTER TABLE to fix upgrade on older installs 9a414d
-
feat(publication-selector): replace emoji icons with Phosphor ph-* icons
- Draft: 🟡 → ph-pencil-simple (yellow)
- Published: 🟢 → ph-check-circle (green)
- Publication item: 📦 → ph-archive, ↩️ → ph-arrow-counter-clockwise
- Active check: ✓ → ph-check
- Publish button: 💾 → ph-floppy-disk
- Load more: ⬇️ → ph-arrow-down
- All loaded: ✅ → ph-checks
- item-badge-live: green pill → plain item-meta style (hover-reveal preserved)
- Add issue 017 comparison/decision HTML doc caad34 -
docs: update dashboards to reflect current state (2026-03-18)
- issues/DASHBOARD.md: task 006 (light theme) marked Done, all 16 issues closed
- DASHBOARD.md: full rewrite — phases 1-4 completed, phase 5 at 80%,
all features listed as completed, metrics updated (50 PHP test files,
28 JS spec files, 512 PHP tests) ad64ab -
feat(settings-editor): prompt to switch to Draft when clicking in read-only mode
When the panel is in PUBLISHED or PUBLICATION mode, a delegated click guard
intercepts any click on the (disabled) fields area and shows a native confirm()
asking the user to switch to Draft. On confirmation it fires
bte:requestSwitchToDraft which publication-selector listens to and calls
_switchStatus('DRAFT'), restoring full editability. 508ad9 -
fix(settings-editor): extract theme name from error text instead of using stale this.themeName
When the user switches scope (e.g. from a Breeze store view to 'All Store
Views'), this.themeName still holds the name of the previously loaded theme.
If the config load for the new scope fails, the error message incorrectly
shows the old theme name (e.g. 'Breeze Evolution doesn't support Theme
Editor') instead of the actual problem theme (e.g. Magento Luma).
The backend error already contains the correct name:
'Theme editor configuration file not found for theme: Magento Luma'
Fix: parse theme name via /for theme:\s*(.+)/i from the raw error text in
both _getFriendlyMessage() and _getNoSettingsToastMessage(). Fall back to
this.themeName only when the pattern doesn't match. 45607c -
fix(publication-selector): remove loading state after publish, add Live badge to active publication
- fix loading overlay (bte-loading + pointer-events:none) not removed after
successful publish — loading.hide() was only called in catch, not in .then()
- add 'Live' badge to the currently active publication in the history list
to clarify why its delete button is absent; badge appears on hover like
the delete button does 558298 -
fix(settings-editor): show scope-aware error messages when theme has no BTE config
Previously all three scopes got the same generic message:
'Theme Editor is not available for "Theme" theme. This theme doesn't have
the required configuration file. Please select a different store...'
Two problems with that:
1. 'this.themeName' was never refreshed after a successful config load, so it
could show a stale name from the previous scope (or the PHP-init value).
2. The message text made no sense for scope='default' (All Store Views) or
scope='websites' — telling the user to 'select a different store' without
explaining WHY the current scope has no settings.
Changes:
- _loadConfig: update this.themeName from config.metadata.themeName on every
successful load, just like themeId is already updated.
- _getFriendlyMessage: replace static map with scope-specific branches:
default → explains that the Default Config theme lacks BTE support
websites → explains that the website-level theme lacks BTE support
stores → keeps a store-view-specific message
- _showErrorToast: same scope-aware logic, extracted into _getNoSettingsToastMessage()
- Fix variable shadowing bug in _showErrorToast (var message re-declared inside
the isInvalidToken branch, shadowing the function parameter) d4e7b5 -
Do not intercept URL clicks when Content Builder is active 5ef186
-
feat(menu): move Theme Editor to Swissup > Breeze section
- Add Breeze submenu under Swissup_Core::swissup (sortOrder=10)
- Move Theme Editor menu item under new Breeze group
- Move ACL editor resources from Magento_Backend::content to Swissup_Core::swissup
- Add Swissup_Core to module sequence in module.xml
- Add swissup/module-core to composer.json require e82e4a -
fix(mock-helper): align mockGetCss key shape with actual getCss variables
mockGetCss was building the mock lookup key with flat {storeId, themeId}
fields, but get-css.js passes a nested {scope: {type, scopeId}} object to
client.execute. The JSON.stringify mismatch caused the mock to never match,
making the 'Discard Published — Preview Refresh: getCss should be called
with PUBLISHED status' test time out. 9c792f -
fix(scope-selector): remove URL param reading — use cookies only for scope persistence
The history.replaceState approach (f83df04) introduced potential conflicts
with Magento's own admin URL parameters (?scope=, ?scopeId= are used
internally by Magento itself). BackendSession cookies are the correct
persistence layer and are sufficient on their own.
- AdminToolbar: remove URL param priority from getScope() and getScopeId()
- scope-selector.js: remove history.replaceState block (no longer needed)
- AdminToolbarTest: remove URL param test cases, tighten assertions c53eaa -
fix(scope-selector): use .dropdown-item instead of .scope-store for shared clickable item styles
After removing 'scope-store' class from the All Store Views element,
the shared clickable item styles (flex layout, padding, hover, active)
were only applied to store view items but not to the default/website items.
Replace .scope-store with .dropdown-item as the CSS selector for shared
styles — .dropdown-item is present on all three clickable item types
(All Store Views, website, store view). The remaining .scope-store
selectors for nested indent and .scope-has-settings active state are
kept as-is since they correctly target only store view items. f72591 -
fix(scope-selector): remove scope-store class from All Store Views item
The scope-default-item element had both 'scope-store' and 'scope-default-item'
classes. This caused two click handlers to fire on the same click:
1. .scope-default-item → correctly called _selectScope('default', 0, ...)
2. .scope-store → called _selectScope('stores', 0, ...) with hardcoded
'stores' scope, overwriting the correct selection with stores:0,
writing wrong cookies, setting /store/0/ in the iframe URL, and
breaking the publication selector (scopeId=0 treated as missing).
Fix: remove 'scope-store' class from the All Store Views element so only
the dedicated .scope-default-item handler fires. a64022 -
test(StoreDataProvider): mock getWebsite() and add getDefaultGroup/getDefaultStore to helpers
After adding getDefaultStoreId() to StoreDataProvider, getHierarchicalStores()
now calls storeManager->getWebsite(true) to resolve the default website.
The existing tests did not mock this call, causing null->getId() errors.
- Add getWebsite willReturnMap([true, website], [id, website]) to all three tests
- Add getDefaultStore mock to makeGroup() helper (defaults to first store)
- Add getDefaultGroup mock to makeWebsite() helper (defaults to first group) c21719 -
fix(scope-selector): use Magento default store view for All Store Views and Website scopes
Previously, the preview store for 'All Store Views' was the first active
store found across all websites (iteration order), and for a Website scope
it was the first store alphabetically. Neither respected Magento's
configured default store view.
Changes:
- StoreDataProvider: add getDefaultStoreId(websiteId, fallback) that
resolves via website → defaultGroup → defaultStore, matching Magento's
own default store view logic
- StoreDataProvider: rename previewStoreId → defaultStoreId in hierarchy
data to reflect what the value actually represents
- scope-selector.js: rename _findPreviewStoreId → _findDefaultStoreId,
update all references to defaultStoreId
- scope-selector.js: rewrite _findStoreCode to resolve store code via
defaultStoreId for both 'default' and 'websites' scopes instead of
picking the first array entry 673e7c -
fix(scope-selector): fix click handler selector for All Store Views item
The event listener was bound to '.scope-default' but the template renders
the element with class 'scope-default-item'. The mismatch caused clicks on
'All Store Views' to fall through to the generic '.scope-store' handler,
which set scope='stores' and scopeId=0 instead of scope='default' and
scopeId=0. This produced an empty storeCode, broken cookies, and errors
in the publication selector. b04ad7 -
fix(cs): use correct phpcs:ignore code for ArrayObject restriction 5be53e
-
fix(cs): replace forbidden serialize/unserialize with json_encode/json_decode in ArrayObjectStub 7a688c
-
refactor(scope-selector): rename item-text to item-name and fix empty label on website scope
.item-text was an inconsistent class name used for clickable item labels
(All Store Views, store views). Website items used .scope-name instead,
which is semantically reserved for non-clickable headers (website/group
headers in .scope-header). This caused JS find('.item-text') to return
an empty string when selecting a website scope, leaving the toolbar
button label blank.
- Rename .item-text → .item-name across template, LESS and JS
- Use .item-name on the website clickable item instead of .scope-name
- .scope-name remains on non-clickable group/website header labels 6bcabf -
fix(scope-selector): persist scope in admin URL so F5 restores correct selector state
After switching scope (e.g. to 'All Store Views'), the JS correctly wrote
bte_last_scope/bte_last_scope_id cookies, but the admin page URL still
contained the old scope/scopeId query params from the initial page load.
AdminToolbar::getScope() gives URL params higher priority than cookies, so
pressing F5 reloaded the old URL and reverted the scope selector to the
previously selected store view instead of the one the user switched to.
Fix: call history.replaceState() immediately after the cookie writes to
update the browser URL with the new scope/scopeId values. This mirrors the
pattern already used by iframe-helper.js::syncUrlToParent() for the /url/
path segment. No PHP changes required. f83df0 -
fix(scope-selector): force iframe reload when URL is unchanged after scope switch
When switching to 'All Store Views', the resolved previewStoreId may be
identical to the currently loaded store, making the constructed admin
redirect URL equal to the current src. Browsers treat setting an iframe's
src to the same value as a no-op, so the preview never reloaded.
Navigate via contentWindow.location.href in that case, which always
re-issues the request regardless of URL equality. a6b703 -
fix: render nav icons via icon-registry instead of raw text
navigation.js now maps items through iconRegistry.render() before
passing to template, so named Phosphor icon strings like 'pencil-simple'
produce <i class="ph ph-pencil-simple"> instead of plain text. 6f731d -
refactor: replace custom inline SVG icons with Phosphor Icons
Replace all custom handcrafted SVG icons in HTML templates and PHP
with equivalent Phosphor Icons (<i class="ph ph-*">) to unify icon
style across the UI.
Replaced:
- chevron-down → ph-caret-down (page/scope/publication selectors)
- gear/settings → ph-gear (scope-selector badges, was already Phosphor path)
- pencil/edit → ph-pencil-simple (settings-editor header, AdminToolbar.php)
- moon/sun → ph-moon / ph-sun (dark/light theme toggle)
- search → ph-magnifying-glass (settings-editor search bar)
- exit arrow → ph-sign-out (exit-button)
- eye-slash → ph-eye-slash (toolbar-toggle)
- cursor → ph-cursor (highlight-toggle)
Kept: Magento logo SVG (admin-link.html) — brand icon, no equivalent. ad05a7 -
fix(toolbar): restore storeId and storeCode to getToolbarConfig()
Commit 5d43943 (multi-scope) removed storeId/storeCode from the JS
config in favour of scope/scopeId, which silently broke three JS paths:
- toolbar.js link interceptor guard (config.storeCode always falsy)
- page-selector initialisation (empty storeCode passed in)
- StorageHelper.init() in index.phtml (toolbarConfig.storeId missing)
Add a private getStoreCode() helper and put both keys back. 1ad64f -
fix(cs): replace self-closing path tags with explicit closing tags in scope-selector 618ab5
-
fix(cs): replace restricted ArrayObject with custom stub in tests 62d953
-
refactor(resolver): extract PublicationDataTrait to eliminate duplicated publication array formatting da1540
-
fix(cs): replace self-closing SVG tags with explicit closing tags 209fd1
-
feat(light-theme): add light panel theme with toggle (issue-006)
Added 13 Figma design tokens (@bte-panel-*) to _variables.less.
New _panels-light.less (~737 lines) overrides all panel components
under .bte-panel--light modifier: header, search, sections, fields,
range slider (linear-gradient fill), palette, font-picker, footer.
_theme-editor-panel.less: panel title set to 12px/uppercase,
scrollbar-gutter: stable added to .bte-panel-content.
HTML template: sun/moon toggle button in panel header.
JS _initPanelTheme() + _togglePanelTheme() with localStorage persist;
default theme is light. 7c7423 -
fix(palette-section): palette/font-palette accordion stays interactive in PUBLISHED mode
Introduced baseSectionRenderer jQuery widget that listens to
bte:editabilityChanged and applies disabled state only to $content,
leaving the accordion header always clickable.
palette-section and font-palette-section now extend it via _super().
settings-editor initialises both palette widgets before triggering
bte:editabilityChanged so listeners are registered in time;
direct $paletteContainer enable/disable calls removed. 445764 -
fix(range): restore updateFill method and wire --range-fill on input
Removed duplicate out-of-scope methods that broke the file syntax.
Added updateFill() to set --range-fill CSS custom property used by the
light-theme linear-gradient track; called on input and on init. 800085 -
fix(scope-selector): apply .scope-store styles to All Store Views item
The .scope-default-item sits directly in .scope-hierarchy, not inside
.scope-stores, so it received no styles after the multi-scope refactor
in 5d43943. Lifted shared .scope-store styles to .scope-hierarchy level;
kept padding-left: 56px indent scoped to .scope-stores .scope-store only. 2ea502 -
docs: mark Color Palette System as completed 89c419
-
docs: add issue files 014–016; update DASHBOARD ebf99e
-
test: add 2D matrix tests (scope chain × theme hierarchy) and getThemeIdByScope tests
ValueInheritanceResolverTest: tests I–M cover mixed scope+theme combinations
(parent-theme value found at default/0 or websites/N, child-store overrides,
end-to-end save/read scenario); tests R–S cover buildScopeChain edge cases
(default always single-entry, graceful degradation when StoreManager throws).
ThemeResolverTest: tests N–Q cover getThemeIdByScope for all three scope
types (default, websites, stores) and the LocalizedException path. 2c4c3e -
fix(issue-016): default scope fallback changed from 'stores' to 'default' 8071f7
-
test(issue-016): add regression test — getScope() falls back to 'stores' instead of 'default' 7fdabb
-
fix(issue-015): PUBLISHED CSS now uses scope chain and theme hierarchy
CssGenerator::generate() PUBLISHED branch was calling
ValueService::getValuesByTheme() — a flat single-scope DB query that ignores
parent scopes (default/websites) and parent themes. Values saved at a broader
scope or in a parent theme were silently dropped from the generated CSS.
Fix: replace the flat query with ValueInheritanceResolver::resolveAllValues()
which walks the full scope chain (default → websites → stores) and merges
values across the theme hierarchy, identical to what DRAFT already does via
resolveAllValuesWithFallback().
ValueService is no longer a direct dependency of CssGenerator — removed from
constructor and use statement.
Tests: updated 42 PUBLISHED test mocks (getValuesByTheme → resolveAllValues);
regression tests 44-45 now pass. 701388 -
test(issue-015): add regression tests 44-45 — PUBLISHED branch bypasses scope chain
Tests 44 and 45 document the bug in CssGenerator::generate() PUBLISHED branch:
- testPublishedCssIncludesValuesFromBroaderScope: value saved at default scope
must appear in PUBLISHED CSS for a stores/1 request
- testPublishedCssIncludesValuesFromParentTheme: value from parent theme must
appear in PUBLISHED CSS for child theme
Both tests assert resolveAllValues() is called (not getValuesByTheme()).
Both tests FAIL before the fix is applied — confirming the bug exists. 9e2730 -
fix(issue-014): resolve websiteId internally via StoreManager in ValueInheritanceResolver
- Inject optional StoreManagerInterface into ValueInheritanceResolver
- Add resolveWebsiteId(): websites/W resolves from scopeId (no StoreManager needed),
stores/N resolves via StoreManager->getStore()->getWebsiteId()
- buildScopeChain() auto-calls resolveWebsiteId() when websiteId=0
- Fixes broken inheritance chain: stores/N and websites/W now receive
default/0 and websites/W values as expected
- Add 2 bonus tests (G, H) covering websites/W auto-resolution without StoreManager
- All 520 unit tests pass 445d55 -
test(issue-014): add scope-chain StoreManager tests — 2 document bug, 4 fail until fix 576ebb
-
fix(tests): sync tests with scope refactor — ScopeFactory mock, storeId→scopeId b4c0aa
-
fix(coding-standard): remove final keyword from Scope class 3ab148
-
refactor(scope): propagate ScopeInterface to all services, resolvers and tests 88c208
-
refactor(scope): replace scope+scopeId params with ScopeInterface value object
Introduces Api/Data/ScopeInterface, Model/Data/Scope, and Model/Data/ScopeFactory.
Updates ThemeResolver::getThemeIdByScope() and all resolvers to accept a ScopeInterface
VO instead of separate string+int arguments. Updates all unit tests accordingly. a35a71 -
fix(graphql-tests): update integration test queries to use BreezeThemeEditorScopeInput 16cd18
-
fix(publications): complete scope refactor in Publications resolver and test ce4e75
-
refactor(schema): replace scope+scopeId pairs with BreezeThemeEditorScopeInput 6c2d31
-
fix(discard-published): clear stale PUBLICATION state from localStorage and iframe on reset
After a successful discardBreezeThemeEditorPublished mutation, if the editor
was in PUBLICATION mode the old publication CSS remained visible both in the
current session (iframe still showed #bte-publication-css) and after a page
reload (localStorage still held status=PUBLICATION / publicationId=N).
- publication-selector.js: reset localStorage to PUBLISHED and clear
currentPublicationId/Title immediately after a successful discard so
the stale publication is never re-applied on next page load
- settings-editor.js: call CssManager.showPublished() before refreshPublishedCss()
in the bte:publishedDiscarded handler so the #bte-publication-css layer is
removed and #bte-theme-css-variables is re-enabled right away without
requiring a page reload 3f4116 -
feat(multi-scope): add Default/Website/Store View scope support
- Add 'scope' column to value and publication tables (db_schema)
- Add MigrateValueScope data patch for existing rows
- ValueInterface/Value/ValueRepository: add scope field
- ValueService/ValueInheritanceResolver: scope+scopeId instead of storeId; scope inheritance chain (default → websites → stores)
- ThemeResolver: new getThemeIdByScope(scope, scopeId) method
- PublishService: accept scope+scopeId params
- StoreDataProvider: expose Default and Website entries in hierarchy
- AdminToolbar: scope+scopeId instead of storeId
- All Query/Mutation resolvers: migrate to scope+scopeId
- schema.graphqls: new BreezeThemeEditorScopeCode enum; rename fields
- JS: scopeChanged event, scope+scopeId in all GraphQL calls and state
- All Unit tests updated to match new method signatures (512 pass) 5d4394 -
fix(publication-selector): move delete button inside <a> flex row after item-check 49e475
-
fix: remove overflow:hidden from bte-font-palette-container to prevent dropdown clipping
.bte-font-picker-dropdown was being visually clipped by overflow:hidden on
.bte-font-palette-container. Removed overflow:hidden and preserved border-radius
appearance by applying rounded corners directly to first/last child elements. 4a58f4 -
fix(palette-manager): preserve listeners across init() calls; add reset guard tests
- Remove this.listeners = [] from PaletteManager.init(): wiping listeners on
every _loadConfig() (store-switch, config reload) silently disconnected
CssPreviewManager from Pickr colour changes — live preview stopped updating
and localStorage was not written after the first config reload.
dirtyColors is still reset on init (correct: fresh config = no unsaved state).
- Add global-reset-palette-test.js (10 tests): regression suite for the
_reset() guard bug where palette-only Pickr changes triggered 'No changes
to reset' toast (fixed in e0cdfaa). Covers guard logic, revertDirtyChanges()
side-effects, and full reset lifecycle.
- Add palette-manager-init-listeners-test.js (8 tests): documents the
listeners preservation fix with old-vs-new side-by-side proof, verifies
subscribers survive re-init, and guards that dirtyColors/palettes are still
correctly updated on each init() call.
- Register both test suites in Block/TestRunner.php getAdminTestModules(). 4115b0 -
refactor(pickr): remove Save button; add Cancel button and handler in both Pickr instances
- color.js: save: false (live-commit via on('change') makes it redundant);
remove pickr.on('save') handler; keep cancel: true and restore on('cancel')
to call _handleColorChange so the revert is persisted before popup closes
- palette-section-renderer.js: cancel: false → true; add on('cancel') handler
that restores originalHex in swatch visual, PaletteManager, badges and panel 0e7c49 -
fix(reset): use namespaced events in palette section; fix reset button state on init and after reset 8a7d34
-
fix(reset): include palette dirty changes in global reset check and revert e0cdfa
-
refactor(palette): remove Save/Cancel buttons; live-commit on every colour change
Replace the Save/Cancel Pickr interaction model with a fully live one:
- Remove save/cancel buttons from Pickr interaction config
- pickr.on('change') now calls PaletteManager.updateColor() immediately so
every drag/input is committed as a dirty change in real time
- Skip the first 'change' event fired by Pickr during initialization to
avoid marking a colour as dirty when the picker is just opened
- Badge and paletteColorChanged updates are debounced 150 ms to prevent
excessive re-renders while the user drags a slider
- _justChanged cooldown moved into the debounced callback (still 500 ms)
- Remove pickr.on('save') and pickr.on('cancel') handlers entirely 0ef2ec -
fix(palette): use notify() for live preview; prevent false dirty state on change/cancel
- pickr.on('change'): replace updateColor() with notify() so dragging the
colour picker does not mark the colour as dirty or create a dirtyColors
entry before the user confirms with Save
- pickr.on('cancel'): same — restore the swatch CSS via notify() instead of
updateColor(), so cancelling a pick never leaves a stale dirty entry
- color.js outside-click: exclude .bte-palette-swatch from _closeAllPopups()
so palette-swatch Pickr popups are not closed by the field-handler's handler
- palette-section-renderer.js: remove duplicate 'use strict'; update comment
to reflect Pickr (was 'Native color picker') ffffc0 -
feat: replace native color input with Pickr in palette swatches
- Replace <input type="color"> in palette section with Pickr popup
(same picker already used by config color fields)
- Add opacity/alpha-channel support for palette colors (#rrggbbaa)
- Add live preview via pickr.on('change') + PaletteManager.updateColor()
- Add Cancel button that restores the original color
- Add ESC and outside-click handlers to close popup
- Add viewport clamping in _positionPickrPopup (flip left when near edge)
- Remove .bte-swatch-input CSS rule (hidden input no longer exists)
- Add palette-pickr-test.js (20 tests: normalizeHexAlpha, positioning, save/cancel logic)
- Update palette-reset-behavior-test.js comment (remove native input reference)
- Register palette-pickr-test in Block/TestRunner.php 7e0191 -
feat(font-picker): support local theme fonts via relative url path
Options with a theme-relative url (e.g. 'web/fonts/MyFont.woff2') are now
valid in font_picker config. The module skips @import for non-http urls —
local fonts are loaded by the theme's own @font-face rules. The same filter
is applied in AbstractConfigResolver so local paths are excluded from
fontStylesheets (preventing broken <link> attempts in the admin UI).
Tests: CssGeneratorTest #43, AbstractConfigResolverFontStylesheetsTest #6-7. 22f9d9 -
fix(css-generator): resolve @import for palette-role font_picker fields
buildFontImports() was reading $field['options'] which is always empty
for fields that use font_palette. Now falls back to
config['font_palettes'][$paletteId]['options'] when font_palette is set.
Both callers (generate / generateFromValuesMap) pass $config through.
Tests 39-42 added to cover: palette import emitted, CSS-var consumer
produces no import, web-safe font produces no import, duplicate URLs
deduplicated to one @import. 5b2ba2 -
docs(issues): add issues 012-013, sync dashboard to Mar 9 60f7b1
-
docs: close issue 009 — all acceptance criteria met 4c14c1
-
style: add delete-publication button styles to publication selector 9cd89b
-
fix(publication-selector): pass canDeletePublication and activePublicationId to template renderer e3cf40
-
fix(preview): refresh iframe CSS after discarding published changes
bte:publishedDiscarded event was fired by publication-selector but had
zero listeners, so #bte-live-preview and #bte-theme-css-variables in the
iframe were never updated — the preview kept showing the old red header.
- css-manager.js: add refreshPublishedCss() — re-fetches PUBLISHED CSS
via getCss GraphQL query and writes it into $publishedStyle in iframe
- settings-editor.js: add bte:publishedDiscarded listener that calls
CssPreviewManager.reset(), CssManager.refreshPublishedCss(), _loadConfig()
- discard-published-preview-test.js: 14 tests covering response parsing,
DOM update simulation, event system, regression guards, and MockHelper
async integration (getCss PUBLISHED status + error handling)
- Block/TestRunner.php: register new test suite 9484b6 -
feat: allow deleting old publication versions
- Add deleteBreezeThemeEditorPublication GraphQL mutation + output type
- Add DeletePublicationService with guard: most recent publication for a
theme/store cannot be deleted (it represents the active published state)
- Add DeletePublication resolver (ACL: editor_publish)
- Add delete-publication.js GraphQL client wrapper
- Add delete (×) button to each non-active publication row in the dropdown
- Wire _deletePublication() handler with confirm dialog, optimistic list
update, and fallback to DRAFT when the currently viewed publication is
deleted
- Add DeletePublicationTest (6 unit tests)
Closes #009 71c935 -
fix: explicitly clean FPC entries by tag after Publish/Rollback
Instead of only marking FPC as 'invalid' in the admin UI via cacheTypeList,
also call fullPageCache->clean() with the bte_theme_variables tag so cached
pages are physically removed from FPC storage (Redis/Varnish) immediately. 856b50 -
fix(draft): DRAFT values now inherit PUBLISHED as base layer
Add resolveAllValuesWithFallback() to ValueInheritanceResolver that merges
published rows as a base layer and overlays draft rows on top. Fields without
a draft row now display their published value instead of the theme default.
Update Config, Values, and CssGenerator resolvers to use the new method for
DRAFT status. Update all affected unit tests to mock resolveAllValuesWithFallback
and use willReturnMap for getStatusId (called twice: DRAFT + PUBLISHED). a5c793 -
fix(schema): correct FK table attributes and add db_schema_whitelist to prevent duplicate constraint errors on setup:upgrade 2d1773
-
fix: resolve publish 500 error caused by legacy user_id=0 duplicate rows (issue 011)
- PublishService::publish() — delete all existing published rows (any
user_id) before inserting merged snapshot; wraps in transaction;
saves published values with real userId instead of 0
- PublishService::rollback() — same: delete-all-published + transaction
+ real userId; fixes stale legacy rows surviving rollback
- db_schema.xml — add FK user_id → admin_user (onDelete=NO ACTION);
remove default=0 from user_id column (incompatible with FK)
- PublishServiceTest — add ResourceConnection mock (10th ctor arg);
update deleteValues/getValuesByTheme expectations; rename
testPublishSavesPublishedValuesWithUserIdZero → WithUserId;
add 3 new tests: merge logic, transaction rollback, rollback cleanup 3a0d19 -
fix: restore selected store view after F5 and update changes badge on store switch
- AdminToolbar::getStoreId() now mirrors AbstractEditor priority chain:
URL param → bte_last_store_id cookie → StoreManager fallback.
Fixes regression from fef967c where ViewModel ignored the cookie and
always returned the default store, causing scope-selector to highlight
the wrong store view after page reload.
- publication-selector: add storeChanged listener to reload changesCount
and publications for the newly selected store view. 4ed070 -
docs(issues): update dashboard — sync status after Mar 5-6 commits 33c4f4
-
revert: remove phpstan.neon.dist, fix is in ci.swissuplabs.com instead d64ecb
-
feat(icons): add Phosphor icon support for config sections (issue 008)
- Add icon-registry.js: loads Phosphor Icons webfont from CDN on demand,
renders named ph-* icons, raw SVG, base64 and plain data-URI formats
- settings-editor.js: render section.icon via IconRegistry (drop dead bte-icon-* fallback)
- palette-section-renderer.js: prepend ph-palette icon to Color Palettes title
- font-palette-section-renderer.js: prepend ph-text-t icon to Font Palettes title
- palette-section.html: remove hardcoded emoji, icon injected by JS
- LESS: .bte-section-icon sizing for SVG/img fallbacks; flex layout on
.bte-palette-title and .bte-font-palette-title 689c6e -
chore: add phpstan.neon.dist excluding integration tests from analysis dcbf67
-
Fix bte-panel-title color overridden by admin h2 styles 7d0f61
-
fix: config sections collapsed by default on first visit (issue 007)
Remove fallback blocks that forced the first accordion section open when
localStorage was empty or no saved section codes matched the rendered list.
localStorage persistence was already in place; all sections now start closed. b134b6 -
docs(issues): add issues dashboard 138cd4
-
docs(issues): add issue tracker files for bugs found in QA session (Mar 5) f0c292
-
fix: skip font_picker fields in palette-var processing; fix font preview for CSS-var references (issue 010) 23fe2a
-
test: add unit tests for InvalidateCacheAfterMutation plugin
- SaveValues: block cache invalidated, FPC never touched
- Publish/Rollback: block cache + FPC both invalidated
- failed/non-array/missing-key results: nothing called
- return value passthrough on success and failure 9d7f39 -
fix: cache invalidation not firing after Publish/Rollback (issue 002)
- move plugin registrations from etc/frontend/di.xml to etc/graphql/di.xml
so they are active in the graphql DI area where resolvers actually run
- inject TypeListInterface and invalidate full_page FPC after Publish
and Rollback; SaveValues (draft only) invalidates block cache only
- clean up InvalidateCacheAfterMutation: remove docblocks, flatten
afterResolve early-return, rename invalidateCache -> invalidateBlockCache e135ac -
test: add regression tests for publish/rollback wrong user_id bug
- testPublishSavesPublishedValuesWithUserIdZero: asserts published
values are saved with user_id=0, not the admin's userId
- testRollbackSavesPublishedValuesWithUserIdZero: same for rollback()
- docs/issues/001-publish-service-wrong-user-id.md: full issue writeup b0da45 -
chore: remove completed integration tests plan document d7112c
-
fix: published values saved with admin user_id instead of 0
- setUserId($userId) -> setUserId(0) in publish() and rollback()
- remove FK_VALUE_USER from db_schema.xml (user_id=0 is not a valid
admin_user ID, FK prevents saving global/published rows and causes
cascade-delete when the admin is removed) fb1cb7 -
chore: add bin/test script to run unit and GraphQL tests together db3f29
-
test: add GraphQL API integration tests for ThemeEditor workflow 11c120
-
fix: wrap CSS-var font role references in var() in formatFont()
Font picker fields that reference a palette role (e.g. --primary-font)
were producing "--primary-font", sans-serif in published CSS instead of
var(--primary-font). Added the missing str_starts_with '--' guard in
CssGenerator::formatFont(), mirroring the same check already present in
the JS _formatFont(). Added regression tests 37 and 38 to cover both
primary and secondary font role references. af7049 -
fix: restore _rollbackTo method and refresh publishedModifiedCount after publish
- Close unclosed JSDoc block comment that was swallowing entire _rollbackTo
method body, causing 'TypeError: self._rollbackTo is not a function'
- Reload metadata in bte:published handler so publishedModifiedCount stays
accurate after every publish or rollback without a page refresh e18643 -
feat: add discardPublished mutation to reset published customizations
- Add discardBreezeThemeEditorPublished GraphQL mutation (DiscardPublished.php)
that deletes all PUBLISHED value rows, reverting live site to theme defaults
- Add editor_reset_published ACL resource and canResetPublished permission check
- Add modifiedCount to BreezeThemeEditorMetadata to expose how many published
fields differ from defaults; metadata-loader now fetches PUBLISHED config in
parallel to get this count
- Add discard-published UI button in publication-selector dropdown, shown only
when publishedModifiedCount > 0; requires canResetPublished permission
- Add 7 unit tests for DiscardPublished resolver; update ConfigTest with
modifiedCount coverage (205 lines) 1308f9 -
test: add unit tests for 13 medium-risk classes
- Add 62 new tests across 13 previously-untested classes:
UserResolver (6), AclAuthorization (4), DiscardDraft (4),
ImportSettings (4), Compare (3), CopyFromStore (5),
ResetToDefaults (5), ApplyPreset (5), Publication (4),
Publications (5), ConfigFromPublication (3),
ThemeResolver (7), AdminUserLoader (7)
- Fix bug in ImportSettings: was passing int statusId instead of
string statusCode to ImportExportService::import()
- Update missing-test-coverage.md to mark all 13 classes as covered 6cb138 -
test: add unit tests for ValidationService and SaveValues
Cover the last two untested high-risk PHP classes:
- ValidationServiceTest: 16 cases for all validateValue branches
(field not found, required, color/hex, number/range, text/textarea, unknown type)
and validateValues batch iteration
- SaveValuesTest: 8 cases for batch save, isModified flags, param resolution
and edge cases
Update missing-test-coverage.md to mark all 6/7 high-risk classes as covered. 41c623 -
chore(docs): remove stale plans, sync statuses with completed work
- Remove master-plan.md (severely outdated, wrong architecture)
- Remove refactoring/admin-frontend-alignment/ (completed in ed34804)
- Remove refactoring/css-manager/ (completed 2026-02-17)
- Remove refactoring/js-testing/next-steps.md (self-contradicting)
- Update migration/README.md: Phase 4 → completed, progress 95%, Phase 5 = next
- Update refactoring/README.md: CSS Manager → completed, JS Testing → 24 suites
- Update navigation-panel-integration/README.md: Phase 3 → completed
- Update js-testing/README.md: reflect 24 test files, mark Phase 2+3 complete
- Fix dead links in migration/phases/phase-5/guide.md ff5c11 -
refactor(viewmodel): rename getPublications() to getInitialPublications()
Clarifies intent: these are fast-bootstrap empty values for initial JS config;
real data loads via GraphQL immediately after page load. Removes misleading
@deprecated annotation. Closes issue viewmodel-hardcoded-server-data. 1c3e0d -
chore(docs): delete resolved issues (badge renderer, toolbar dead code, mixed language, magic constants) 529eab
-
refactor: remove unused renderPaletteBadge dead method from badge-renderer 179c21
-
refactor: rename getCurrentPublicationStatus to getInitialPublicationStatus, remove unused statusIndicator config and getDraftChangesCount d8242f
-
refactor: translate Ukrainian inline comments to English in PublishService 02785c
-
fix(code-quality): replace magic integers with UserContextInterface constants in UserResolver 175d38
-
refactor: remove token-based auth infrastructure (AccessToken, TokenManager, EnabledWithLink)
Frontend overlay removed in 7eedd20; token-based toolbar persistence no longer needed.
- Delete EnabledWithLink block, phtml, JS (system config field)
- Delete TokenManager, AccessToken, AccessTokenInterface, AccessTokenValidator
- Remove AccessToken DI preference from di.xml
- Strip AccessToken injection from StoreDataProvider (getStoreUrl no longer appends tokens)
- Remove frontend_model from adminhtml/system.xml enabled field
- Delete dead-code-token-manager.md issue (fully resolved)
Tests: 387/387 pass (2 skipped) 36a6f2 -
refactor: remove stale bte:saved and status indicator dead comments from toolbar b1ac01
-
chore(docs): delete resolved issues (css-manager dead code, module.xml sequence, ACL bypass) 7c2686
-
refactor: remove deprecated _loadAndInjectCSS dead code from css-manager 3353c5
-
refactor: remove unused AccessToken injection from ThemeCssVariables 3c8ea7
-
fix(ui): hide highlight toggle until iframe overlay is implemented f39321
-
chore(docs): remove implemented plans and outdated audit files
All three plans in docs/plans/ are fully implemented in code;
remove them along with the stale AUDIT-SUMMARY.md (18.02.2026)
and PHASE-1B-VISUAL-GUIDE.txt. Update README.md and DASHBOARD.md
to drop broken references. 919c8a -
fix(config): declare missing module dependencies in sequence 0a7031
-
fix(security): replace isLoggedIn() with proper ACL checks in canEdit/canPublish 50fbd5
-
docs(issues): update issue tracker after GraphQL params refactoring
Remove resolved graphql-params-god-object issue and add 13 new issue
files covering ACL bypass, dead code, missing tests, and other findings. b71284 -
fix(ui): persist open/closed state for Color Palettes and Font Palettes sections
Both sections were hardcoded to always open on every render (_render()
unconditionally called addClass('active').show()), so user preference was
lost on every F5.
Add StorageHelper as a dependency to both renderers. _render() now reads
'palette_open' / 'font_palette_open' from scoped localStorage and only
opens the section when the stored value is not 'false' (null = first visit
→ open by default, preserving existing UX). The toggle handler in _bind()
writes the new state on every click. b50063 -
fix(storage): re-init StorageHelper after themeId resolves from GraphQL metadata
Without this, a store-switch (which temporarily sets themeId=null) or an
initial PHP themeId=0 fallback would cause accordion section state to be
saved under the unscoped key 'bte_open_sections'. On the next F5, when
PHP correctly resolves the themeId, StorageHelper would read from the
scoped key 'bte_{storeId}_{themeId}_open_sections' and find nothing —
losing the open/closed section state entirely.
The existing migration logic in StorageHelper.getItem() handles the
unscoped → scoped key transition automatically, so no data is lost. fc4bbd -
refactor(graphql): replace BreezeThemeEditorFieldParams God Object with interface + concrete types
- Replace flat BreezeThemeEditorFieldParams with BreezeThemeEditorFieldParamsInterface
and 6 concrete types (Numeric, Select, FontPicker, SocialLinks, ImageUpload, Code)
- Add FieldParamsTypeResolver to dispatch _fieldType key to correct GraphQL type
- Rewrite AbstractConfigResolver::formatParams() with match + 6 private builders
- Fix B1 naming mismatch: allowedExtensions/maxFileSize -> acceptTypes/maxSize
- Update JS GraphQL queries to use inline fragments instead of flat params block
- Add AbstractConfigResolverParamsTest and FieldParamsTypeResolverTest
- Add _fieldType assertions to existing FontStylesheets tests cbd36d -
fix(ui): move palette section padding to content area and fix template comment syntax
- Move padding from .bte-palette-section to .bte-palette-content so the
accordion header stretches full-width with its own 12px 16px padding
- Replace ERB-style HTML comments (<%-- --%>) with JS block comments (/* */)
in font-picker.html to avoid template parse warnings 213b3f -
fix(tests): fix all 46 failing JS tests for Font Palettes feature
- Add assertEqual(expected, actual) to test-framework.js createTestContext()
to fix 30 failures in FontPaletteManager and Section Renderer Layer 1 tests
- Rewrite Layer 2 DOM infrastructure in font-palette-section-renderer-test.js:
replace broken $.widget instantiation with inline buildSectionHtml() +
bindHandlers() pattern (proven approach from font-picker-test.js); patch
PanelState.setValue instead of BadgeRenderer.renderPaletteBadges; remove
badge-renderer and font-palette-section-renderer from define() deps
- Update TestRunner.php test count comment (25 → 33 tests) 7bd2bb -
test(font-palette): add missing test coverage for Font Palettes feature
- FontPaletteProviderTest.php (14 tests): covers all provider logic — empty config,
palette id/label/options/fonts, multiple palettes, themeId forwarding
- AbstractConfigResolverFontPalettePassthroughTest.php (4 tests): covers fontPalette
passthrough in mergeSectionsWithValues() for font_picker fields
- font-palette-manager-test.js (+5 tests): covers setCurrentValue/getCurrentValue API
and re-init clearing of stored live values
- font-palette-section-renderer-test.js (25 tests): pure-logic layer (escapeHtml,
buildRoleMap, badge counting, restore visibility) + DOM/widget integration layer
(render, role rows, picker widgets, dropdown, option click, accordion, stylesheets)
- TestRunner.php: register font-palette-section-renderer-test, update comment d392ac -
fix(font-palette): track live role values in FontPaletteManager for accurate swatch rendering
- Add setCurrentValue()/getCurrentValue() to FontPaletteManager so
resolveValue() returns the user's actual selection, not just the
schema default
- Seed current values from config.sections immediately after
FontPaletteManager.init() (before _renderSections) so consumer-field
role swatches render in the correct typeface on initial load
- font-palette-section-renderer._buildRoleMap() and all role change/
reset/restore handlers now keep FontPaletteManager in sync
- font-picker.js renderer uses getCurrentValue() instead of role.default
for role swatch data-font-family + style attribute bb5014 -
fix --base-font-family: null when consumer field set to role reference
The hidden <select> only had options for direct font-family values, so
$select.val('--primary-font') could not match any option and returned
null on the subsequent .val() call. The change handler then stored
null in PanelState and CssPreviewManager wrote '--base-font-family: null'.
Add hidden <option> elements for each font role reference to the select.
This makes jQuery's .val('--primary-font') resolve correctly so the
change handler reads '--primary-font', PanelState stores it, and
CssPreviewManager writes '--base-font-family: var(--primary-font)'. 2506bc -
fix font picker widget UI not updating after field reset/restore
base.js updateFieldUIAfterReset only calls $input.val() on the hidden
<select>, leaving the custom widget trigger label text and dropdown
is-selected marks stale (e.g. showing 'Primary' after reset to a
direct font value).
Add updateFieldUIAfterReset to simple.js (registered as FONT_PICKER
handler) that syncs the custom widget: updates the hidden select,
clears selection marks, finds the matching option or role swatch,
and updates the trigger label text + font-family style. d4faec -
simplify font palette section: single restore button next to Modified badge
Replace per-role Restore (×) row buttons with a single × button rendered
in the section header immediately after the Modified (N) badge. Clicking
it restores all modified font roles to their theme defaults in one action.
Removes _updateAllRoleRowBadges(), _updateRoleRowBadge(), the per-row
bte-font-role-badge slot, and the bte-font-role-restore-btn click handler. 12fbc1 -
fix(font-palette): add section badges/reset and fix missing role swatches in consumer pickers
- font-picker.js: read field.fontPalette instead of data.fontPalette — base.prepareData
does not copy fontPalette, so role swatches were never rendered in consumer fields
- font-palette-section-renderer.js: add BadgeRenderer dependency; add bte-font-palette-badges
container in the section header; add _updateHeaderBadges() (dirty/modified counts via
PanelState), _updateRolePickerUI() helper, reset button handler (.bte-palette-reset-btn),
and listeners for paletteColorChanged / themeEditorDraftSaved to keep badges in sync
- _font-picker.less: add .bte-font-palette-badges flex layout rule c55270 -
feat(font-palette): add font palette section renderer and wire it into the panel
- Add font-palette-section-renderer.js jQuery widget (swissup.fontPaletteSection)
that renders role pickers (Primary / Secondary / Utility) outside the accordion,
mirroring the color palette section architecture
- Wire widget into settings-editor.js via _initFontPaletteSection(); add
bte-font-palette-container div to settings-editor.html template
- Skip palette-role fields in field-renderer.js (they render in the font palette
section, not in the accordion)
- Add LESS styles for .bte-font-palette-section, .bte-font-role-row, etc. 498f20 -
fix(tests): add missing FontPaletteProvider mock to Config resolver tests 97d150
-
feat(font-palette): implement font palette reference system
Add FontPaletteManager with init/getPalette/getOptions/getFonts/getRole/isPaletteRole/getStylesheetMap/resolveValue.
Wire font_palette field attribute end-to-end: GraphQL schema, PHP resolvers, JS renderer, field handler, CSS preview
manager, and settings-editor init. Font role swatches appear above the options list for consumer fields; role fields
show only the options list to avoid self-referencing. Selecting a role swatch stores a CSS-var ref (--primary-font)
as the value; CssPreviewManager injects the role's default font stack so var(--primary-font) resolves in the preview.
Add role swatch LESS styles, unit tests, and TestRunner registration. 2f9259 -
feat(font-picker): replace native select with custom div-based dropdown
Each option now renders in its own typeface by replacing the OS-rendered
<select> with a custom div dropdown. The hidden native <select> is kept as
the source of truth so existing change handlers require no modification.
Google Font stylesheets are loaded into the admin document head on open,
avoiding duplicate injections. Includes dedicated LESS styles and 20 new
unit tests (14 DOM-based interaction tests + 6 renderer tests). 5ed6d0 -
test(font-picker): add unit tests for buildFontImports, formatFont, and fontStylesheets extraction 9ec691
-
feat(font-picker): mirror selected font-family onto the <select> element
Set style.fontFamily on the <select> on initial render (via template),
on change (simple.js handler), and on syncFieldsFromChanges restore
(css-preview-manager.js) so the dropdown visually shows the active font. 36cf88 -
feat(font-picker): add url support for external font stylesheets
- Add BreezeThemeEditorFontStylesheet type to GraphQL schema and
fontStylesheets field on BreezeThemeEditorFieldParams
- Extract url entries from options in AbstractConfigResolver::formatParams()
and expose them as fontStylesheets in GraphQL response
- Add fontStylesheets to get-config and get-config-from-publication queries
- Build fontStylesheetMap in font-picker renderer for value→url lookup
- Inject <link> into preview iframe via CssPreviewManager.loadFont() on
font picker change and on initial panel render (_preloadFontStylesheets)
- Prepend @import rules for active Google Fonts in CssGenerator output
- Fix formatFont() to pass through font stacks that already contain commas
- Fix data-font-stylesheets attribute to use HTML-escaped output (<%- %>)
so JSON with quoted font names is not broken by the HTML parser 8e3693 -
Fix icon style in toolbar 95cb23
-
fix(cs): replace self-closing SVG tags with explicit closing tags
Magento Coding Standard requires non-void HTML elements to use explicit
closing tags. Changed <circle .../> and <path .../> to <circle ...></circle>
and <path ...></path> in the search icon SVG. a92eef -
test(toastify): add 17 JS tests for the Toastify toast notification library
Groups covered:
1 DOM structure — container appended to body, message HTML, icon emoji, close button
2 Type variants — success/error/notice/warning each get correct CSS class
3 Options & return — closeButton:false, return value, activeToasts counter
4 Container singleton — multiple toasts reuse one container element
5 CSS animation — .toastify-show class added after 10ms delay (async)
6 hide() — DOM removal, counter decrement, container cleanup (async)
7 hideAll() — removes all active toasts (async)
8 Shortcut methods — success/error/notice/warning() helpers
9 Close button click — .trigger('click') causes toast removal (async)
Each test calls setup() to reset singleton state (, activeToasts)
and passes duration:0 to prevent auto-hide from interfering with assertions.
Async tests use this.waitFor() + try/catch/done(e) pattern. e08f8c -
feat(search): add live search for theme settings panel options
Add debounced search input to the theme editor panel that filters
fields by label and description text. Sections with no matches are
hidden; matching sections auto-expand. HEADING dividers are suppressed
during search. Clearing the query restores the accordion state from
localStorage. 9b63a4 -
feat(toastify): add CSS stylesheet so toast notifications are visible
- Create _toastify.less with full styles for .toastify-container/toast/show/
close/icon/message and four type variants (success/error/warning/notice/info)
- The toastify.js library was purely CSS-class-based with zero inline styles,
so all toasts were in the DOM but completely invisible (unstyled block divs)
- Container uses position:fixed top-right at z-index 10200 (above toolbar/dropdowns)
- Slide-in animation: translateX(110%)→0 + opacity 0→1 on .toastify-show
- Add @bte-toast-z-index: 10200 variable to _variables.less
- Import _toastify.less from _module.less
- Add _showToast() calls in field-restore handler: success + error + catch paths e9ca63 -
fix(restore): delete field row on restore-to-default instead of re-saving default value
- ValueRepository::save() now uses insertOnDuplicate to prevent unique constraint
violations when a row already exists for the same composite key
- discardBreezeThemeEditorDraft mutation accepts optional fieldCodes argument
to delete a single field's draft value
- ValueService::deleteValues() filters by setting_code when fieldCodes provided
- DiscardDraft resolver passes fieldCodes from mutation args to the service
- settings-editor.js field-restore handler calls discardDraft instead of saveValue,
so clicking × deletes the DB row rather than upserting the default value
- Added 3 unit tests covering fieldCodes filter behaviour in ValueService 7373f4 -
Added changes to allow Breeze Content Builder integration 4f0d10
-
fix(settings-editor): live preview not updated when discarding field changes
The field-reset and field-restore handlers in settings-editor.js read
$field.attr('data-css-var') to resolve the CSS variable name, but the
color field template renders data-property (not data-css-var), so
fieldCssVar was always undefined → the if-guard failed silently →
CssPreviewManager.setVariable/removeVariable was never called.
Fixes:
- Use $field.attr('data-property') || $field.attr('data-css-var'),
matching how BaseHandler.extractFieldData() already reads it
- Use $field.attr('data-default') instead of $field.attr('data-default-value')
to match the actual attribute name rendered by the template
- Add .first() to the selector since a color field renders two elements
with [data-section][data-field] (trigger div + input)
Adds regression test suite settings-editor-reset-test.js (10 tests)
covering attribute-reading logic, dispatch routing, and before/after
proof of both attribute-name bugs. 9b3d6e -
feat(color): add alpha channel (hex8) support to Pickr color picker
- Enable opacity slider in Pickr (lockOpacity: false, opacity: true)
- Add _normalizeHexAlpha() to strip 'ff' suffix from fully opaque hex8
- Update hex validation regex to accept #rrggbbaa in field-handler,
field-renderer, CSS template, and DOM color-utils
- PHP: ColorConverter::isHex() and hexToRgb() updated for hex8 support
- PHP: ColorFormatter and CssGenerator output rgba() for hex8 + format=rgb
- Backward-compatible: existing #rrggbb values work without migration
- 12 new PHP tests + 9 new JS DOM color-utils tests (all passing)
Also: add inheritParent flag to ConfigProvider and ValueInheritanceResolver
so child themes can opt out of parent config/value inheritance 8d1c57 -
feat(inheritance): add inheritParent flag to opt out of parent theme config/value merging
Add support for 'inheritParent: false' top-level key in settings.json.
When set, both config merging (ConfigProvider) and value inheritance
(ValueInheritanceResolver) skip parent themes entirely. Defaults to true
for backward compatibility. 0f462a -
fix(publication-selector): hide discard-draft button when not in DRAFT mode 1ca396
-
feat(field-renderer): add HEADING field type for UI-only section separators in settings panel 86a7dd
-
fix(publication-selector): full re-render on status switch so rollback button is removed when leaving PUBLICATION mode 06c4df
-
fix(publication-selector): full re-render on publication switch so rollback button follows active row
_loadPublication() was calling updateButton() + updateCheckmarks() (partial
updates) which left the dropdown HTML stale — the rollback button stayed on
the previously active row instead of moving to the newly selected one.
Replace the two partial calls with renderer.render() + _applyPermissions()
so the entire dropdown re-renders with the new currentPublicationId, and the
'↩ Publish this version' button appears under the correct row on next open. d766fc -
fix(publication-selector): show rollback button only for actively previewed publication
The '↩ Publish this version' button is now conditional on
canRollback && status === 'PUBLICATION' && currentPublicationId == pub.id
Previously it rendered for every row whenever canRollback was true.
Now it appears only on the one row the user is currently previewing,
which is the only meaningful context for a rollback action.
Tests: updated GROUP 7 to match new invariant; added GROUP 7b (5 tests)
covering all visibility combinations — DRAFT, PUBLISHED, non-active row,
active row with permission, active row without permission. ae9129 -
feat(publication-selector): add rollback, discard-draft, fix publish button visibility
- Bug 1: forward permissions to window.breezeThemeEditorConfig so
permissions.canPublish() returns the correct ACL value; the Publish
button was never rendered because the key was absent (toolbar.js)
- Bug 2: updateChangesCount() now calls renderer.render() + _applyPermissions()
instead of renderer.updateBadge(), so the Publish button appears after
an external count update (publication-selector.js)
- Bug 3: load-publication click selector narrowed to
[data-publication-id]:not([data-action]) so rollback buttons
(which also carry data-publication-id) no longer trigger _loadPublication
- Backend: PublishService::rollback() now clears the draft before applying
old values, matching publish() behaviour (no orphaned draft after rollback)
- feat: add 'Publish this version' rollback flow — new rollback.js mutation,
_rollbackTo() / _executeRollback() methods, button in each publication row
- feat: add 'Discard draft' button — new discard-draft.js already existed;
wired import, [data-action='discard-draft'] handler, _discardDraft() method
(confirm dialog → mutation → reset changesCount, toast, bte:draftDiscarded)
- style: add .dropdown-rollback-button and .dropdown-discard-button CSS;
rollback is neutral-outlined, discard is red-outlined secondary style;
publish button font-size tightened to 13 px to match
- test: publication-selector-test.js — 10 groups, 37 tests covering
canPublish logic, Bug 1/2/3 regressions, canRollback state, rollback
draft-warning guards, rollback-of-rollback invariant, discard button
visibility (GROUP 9), discard confirm-dialog logic (GROUP 10)
- test: PublishServiceTest::testRollbackToExistingPublication updated to
use willReturnMap for getStatusId (DRAFT+PUBLISHED) and asserts that
deleteValues is called with correct args (draft-clearing regression) ca9c93 -
fix(client): properly reject deferred on GraphQL and HTTP errors
_handleError always throws instead of returning, so wrapping
deferred.reject(self._handleError(...)) caused uncaught exceptions —
deferred was never rejected and the loader stayed forever.
Split xhr.onload into separate try/catch blocks for JSON parse and
_handleSuccess; wrap all _handleError calls in try/catch and reject
the deferred from the catch block. Same fix applied to the HTTP error
and xhr.onerror paths. fe5c16 -
fix: throw GraphQlNoSuchEntityException when theme has no settings.json
When no theme in the inheritance hierarchy has a settings.json the
resolver now throws GraphQlNoSuchEntityException (visible in production,
unlike GraphQlInputException which is masked as 'Internal server error').
Frontend _getFriendlyMessage already matches 'configuration file not found'
and shows a user-friendly panel with a hint to switch stores.
- Config.php: throw after getConfigurationWithInheritance returns empty sections
- ConfigTest.php: fix mocks that used sections:[] for unrelated tests,
add dedicated test for unsupported-theme exception 8c0d48 -
fix: reinit paletteSection widget on store switch, remove stale storeId guard 520b36
-
fix: keep global config in sync when store scope changes
settings-editor is lazy-initialized on first panel open. If the user switches
store BEFORE opening the panel, _create() was still reading the stale
window.breezeThemeEditorConfig (storeId=1, themeId=4 from page load).
Listen for 'storeChanged' in toolbar.js _bindGlobalEvents() and update:
- window.breezeThemeEditorConfig.storeId / themeId (read by lazy _create)
- navigation panelWidgets['theme-editor'].config (also passed to _create)
themeId is cleared to null so the backend resolves the correct theme from
the new storeId automatically. 393e1b -
fix: ignore admin URLs in iframe-helper to prevent recursive toolbar loading
When the iframe loaded an admin URL (e.g. after a redirect to
/admin/breeze_editor/editor/index/...), saveCurrentUrl() stored it to
localStorage and syncUrlToParent() updated the parent window, causing
the iframe to reload the admin editor page recursively and producing
duplicate bte-toolbar-container elements inside bte-preview.
Added isAdminUrl() guard in getCurrentUrl() so any admin path is
treated as inaccessible and silently ignored. 1f9a54 -
fix: remove duplicate bte-toolbar class from shell div in index.phtml
The shell div #breeze-theme-editor-toolbar already received the
bte-toolbar class from the toolbar.html template injected by toolbar.js,
causing .bte-toolbar to be nested inside itself. 426543 -
fix: reload settings panel when store scope changes
settings-editor was initialized once with storeId/themeId from page load and
never reacted to store switching. When the user selected a different store view
via scope-selector, the GraphQL query still sent the original storeId (1) and
themeId (4), returning the wrong theme's settings.
Changes:
- Listen for 'storeChanged' event (bubbled from scope-selector widget)
- Update this.storeId to the new store; clear this.themeId (null) so the
backend resolves the correct theme from storeId automatically
- After config loads, persist the resolved themeId from metadata so subsequent
save/palette operations use the correct theme
- Reset live CSS preview on store change (stale draft no longer applies) 983d88 -
fix: use getConfigurationWithInheritance in CompareProvider and Publication resolvers
Replace bare getConfiguration() calls (which throw LocalizedException when a theme
has no settings.json) with getConfigurationWithInheritance(), which always returns a
valid config by falling back to parent-theme definitions. Fixes potential
"Internal server error" in compare and publication-history GraphQL queries for
themes that inherit settings from a parent. e0affd -
fix(resolver): handle missing settings.json gracefully, surface errors via GraphQlInputException
- ConfigProvider::getMetadata() — try settings.json first, fallback to theme object when file absent
- ConfigProvider::getAllDefaults() — use getConfigurationWithInheritance() to read from full theme hierarchy, not getConfiguration() which fails if active theme has no settings.json
- Config::resolve() — wrap ThemeResolver and StatusProvider calls in try/catch, re-throw as GraphQlInputException so client gets readable message instead of 'Internal server error'
- Config::resolve() — null-safe version field (?? '1.0') for non-nullable GraphQL String! bc90a8 -
docs(dashboard): update for 26.02.2026 — AbstractToolbar merge e857bc
-
refactor(viewmodel): merge AbstractToolbar into AdminToolbar, remove dead deps
- AdminToolbar is now standalone (no extends); implements ArgumentInterface directly
- Inlined the 4 methods actually used: getAdminUsername, getScopeSelectorData,
getPageSelectorData, getStoreId
- Removed 3 unused constructor deps: Helper\Data, AccessToken, App\State
- getPageSelectorData simplified: dropped shouldAddToken/token-in-URL logic
(admin is authenticated; no access token needed in page URLs)
- Removed ~20 dead methods: getScopesJsonData, isHierarchicalMode,
getActiveStorePath*, getCurrentScope, hasMultipleScopes, getPagesJsonData,
getCurrentPageName, getCurrentUrl, getExitUrl, getStoreCode, getThemeName,
getWebsiteId, getBaseCurrency, getLocale, getAccessToken, shouldAddToken
- Removed private createSortOrder helper (unused since getPublications() returns [])
- Eliminated duplicate private field declarations (authSession, request,
storeManager, urlBuilder were stored in both parent and child)
- getThemeId fallback inlined; parent::getThemeId() call removed
- getAdminUrl fallback inlined; parent::getAdminUrl() call removed
- AbstractToolbar.php deleted 1a17ca -
fix(css-preview-manager): guard rgbToHex call with isRgbColor check in syncFieldsFromChanges
Values in the changes object may already be in HEX format when loaded from
localStorage, causing a spurious 'Invalid RGB format' warning and incorrect
fallback to #000000. Convert to HEX only when the value is actually RGB. ce709b -
fix(viewmodel): restore base toolbar as AbstractToolbar, fix AdminToolbar extends 5440d6
-
chore(frontend): remove commented-out toolbar blocks and dead ViewModel/Toolbar 2fa4ac
-
docs(dashboard): update for 25.02.2026 — css-manager fix, frontend overlay removal, JS test count 52b19e
-
chore(frontend): remove dead frontend overlay JS, templates, CSS, and images
The storefront toolbar/overlay mode has been superseded by the adminhtml
editor with iframe preview. All frontend overlay files are unused:
- toolbar block is commented out in layout/default.xml
- test runner is disabled ('Use admin test runner instead')
- adminhtml has its own independent copies of all JS, templates, CSS,
and test suites
Removed:
- view/frontend/web/js/ (~115 files: toolbar, theme-editor, graphql, lib, test)
- view/frontend/web/template/ (~37 HTML templates)
- view/frontend/web/css/ (~27 LESS/CSS files + toolbar styles bundle)
- view/frontend/web/images/ (SVGs duplicated in view/base/web/images/)
- view/frontend/templates/toolbar.phtml
- view/frontend/templates/test-runner.phtml
- view/frontend/requirejs-config.js
Active frontend code (ThemeCssVariables, inline-css-variables.phtml,
layouts) is untouched. 7eedd2 -
chore(badges): remove bullet icon from dirty and palette-changed badges 08bbf7
-
fix(css-manager): recreate live preview style after iframe navigation
Draft styles and live preview are separate: draft comes from GraphQL
(saved values), live preview holds unsaved in-progress changes.
After iframe navigation the new page DOM has neither — draft is
re-created by existing logic, live preview must be re-created here
so pending changes remain visible. f2a61d -
fix(tests): update frontend test fixtures cssVar -> property
- test-fixtures.js: rename cssVar key to property in all palette mock
objects (mockPaletteConfig, mockConfigWithPalettes, mockColorFieldPalette,
mockPaletteWithDuplicates) - palette-manager.js now indexes by color.property
- color-field-palette-ref-test.js: fix direct fixture key reads (lines 123,
150-151) and update Test 10 to use data-property attribute instead of
data-css-var
- palette-graphql-test.js: fix direct fixture key read and update param
description string
- palette-manager-test.js: update comment strings for consistency 22ccda -
fix: update storage-helper path in index.phtml template 28ee30
-
fix(test): clear old unscoped keys in storage-helper-test setup
getItem() falls back to 'bte_{key}' via migration logic, so real editor data
in 'bte_live_preview_changes' was leaking into the 'not set' test. 6be6aa -
test(palette): add PaletteManager.isColorModified unit tests
11 tests covering the regression: Modified badge must reflect the saved DB
value vs. theme default, not the current unsaved in-memory value.
Includes edge cases (no color, no default), both dirty/not-dirty branches,
getModifiedCount with mixed state, and three full-flow integration tests
(updateColor from default, updateColor from non-default, markAsSaved). 92620b -
test(storage): add storage-helper-test.js for JSON-based methods
11 tests covering:
- getOpenSections/setOpenSections: default [], round-trip, scoped key, empty save, corrupted JSON
- getLivePreviewChanges/setLivePreviewChanges/clearLivePreviewChanges: default {}, round-trip,
scoped key, clear removes key, corrupted JSON, cross-theme isolation aa0dd6 -
refactor(storage): move storage-helper to utils/browser, centralize localStorage usage
- Move editor/storage-helper.js → editor/utils/browser/storage-helper.js
- Update require path in 10 files (8 prod + 2 test)
- Add getOpenSections/setOpenSections methods (scoped per store/theme)
- Add getLivePreviewChanges/setLivePreviewChanges/clearLivePreviewChanges methods (scoped)
- Route css-preview-manager through StorageHelper (fixes live_preview_changes cross-theme leak)
- Route settings-editor accordion state through StorageHelper (open_sections scoped per theme)
- Persist and restore open accordion sections across page reload 974557 -
fix(palette): show Modified badge based on saved DB value, not unsaved in-memory value
isColorModified() was comparing the current in-memory color.value (which is
updated immediately on every picker change) against color.default. This caused
the Modified badge to appear as soon as the user changed a color, even before
saving.
Fix: use dirtyColors[property].original.value (the snapshot taken before the
first unsaved change) as the saved value for comparison. If the color is not
dirty, color.value is already the saved value. This matches how regular fields
handle isModified — based on savedValue vs defaultValue, not currentValue vs defaultValue. 44fb8d -
fix(palette): rename cssVar→property across full stack + add field-renderers tests
- Fix get-config-from-publication.js (both trees) querying removed cssVar field
on BreezeThemeEditorPaletteColor (was missing from Phase 3 rename sweep)
- Rename cssVar→property in GraphQL schema, PHP models, JS palette managers,
section renderers, field handlers, HTML templates (28 files, both trees)
- Keep backward compat: PHP reads property??css_var from YAML; GraphQL input
keeps deprecated cssVar field alongside new property field
- Add field-renderers-test.js: 57 assertions covering prepareData() for all 13
renderer types; every test verifies data.property propagation (rename check)
- Register field-renderers-test in Block/TestRunner.php admin suite list f7c2e8 -
refactor(iframe): extract HTML rendering to Block and template
Move redirect HTML from Iframe controller into a dedicated Block class
and phtml template. Extract URL-building logic into buildFrontendUrl().
Fix duplicated $logger property inherited from AbstractEditor. 7ff9b0 -
fix(toolbar): remove redundant position/z-index from toolbar-base fb78fc
-
docs: update DASHBOARD and features/README for selector+property feature
- Add feat(selector+property) + bugfix entry in DASHBOARD.md (commits 1a7279e, f401f6c)
- Update PHP test count 265 → 303 in stats
- Bump overall progress 90% → 92%
- Add selector+property as completed feature in features/README.md 23cf91 -
fix(graphql+js): replace cssVar with property+selector in GraphQL queries and frontend renderers
- get-config.js (adminhtml+frontend): fields block now queries property+selector instead of cssVar
- get-config-from-publication.js (adminhtml+frontend): same fix; palette colors.cssVar untouched
- frontend field-renderers/base.js: use field.property (fallback field.cssVar) → data-property attr
- frontend field-handlers/base.js: read data-property (fallback data-css-var); rename internal cssVar→property
- frontend css-preview-manager.js: query/read data-property (with data-css-var fallback for backward compat) f401f6 -
feat(selector+property): add custom CSS selector support and rename css_var→property
- CssGenerator: group CSS output by resolved selector (setting→section→:root fallback);
support array selectors (joined with ', '); use 'property' field with 'css_var' as alias
- ConfigProvider: add 'selector' to mergeSections() overridable fields list
- AbstractConfigResolver: expose 'property' and 'selector' fields in GraphQL response
- schema.graphqls: rename BreezeThemeEditorField.cssVar→property, add selector field
- Admin JS: rename cssVar→property and data-css-var→data-property across field-renderers,
field-handlers, panel-state, and css-preview-manager
- Theme JSON configs: rename css_var→property in all settings arrays (evolution, blank, docs);
palette color entries keep css_var unchanged
- Tests: add 9 new tests for selector grouping (18-23) and property/backward-compat (15b-15d);
total 303 PHP tests pass (up from 294) 1a7279 -
docs(phase-4): finalize Phase 4 — Admin JS 126/126, Frontend JS pending migration
Confirmed Admin JS: 126/126 pass (browser, 24.02.2026).
Frontend JS tests are not obsolete — they migrate alongside
their corresponding functionality as the frontend is ported. 1aa80f -
feat(config-provider): add disable flag for sections and settings
Allow theme child configs to disable specific sections or settings
inherited from parent config by setting disable: true.
Adds array_values() to re-index after removal.
Adds 4 unit tests (294 total, 917 assertions). 63b47b -
docs(phase-4): complete test audit — 290/290 PHP tests pass, classify 37 JS tests
- Run phpunit: 290 tests, 909 assertions, 2 intentional skips (ConfigProvider file I/O)
- Create test-analysis.md: classify all 37 JS tests (0×A, 5×B, 32×C) — no migration needed
- Create TEST-AUDIT-REPORT.md: full audit with PHP results, JS architecture note, findings
- Update phase-4/README.md: fix counts (232→290 PHP, 12→14 admin JS), mark phase complete
- Update DASHBOARD.md: phase-4 done (90%), phase-5 is next, overall progress 92%
- fix(template): use explicit closing tag for SVG path element (HTML5 validity) f4bedf -
chore(layout): comment out toolbar block in default.xml d7f5ad
-
refactor: replace emoji icon with SVG in panel header cc903e
-
refactor: rename 'Theme Editor' to 'Theme Settings' in UI labels 1873da
-
fix(adminhtml): wrap badge+button pairs in bte-badge-group, fix getDirtyCount typo
- badge-renderer: wrap each badge+button pair in <span class="bte-badge-group">
so Changed↺ and Modified× always stay adjacent regardless of layout
- field-renderers/base: include .bte-badge-group in cleanup selector
- _theme-editor-fields.less: replace margin hacks with flex:1 on label +
inline-flex .bte-badge-group; remove justify-content:space-between
- settings-editor: fix field-restore handler using getDirtyCount() instead
of getModifiedCount() when computing draftChangesCount for publication badge 73e442 -
fix(css-manager): create placeholder element when no published CSS changes exist
When no CSS variables have been published yet, the PHP template skips
rendering #bte-theme-css-variables entirely. Instead of failing after
20 retries, css-manager now creates an empty placeholder <style> element
and continues initialization normally. f87d4a -
feat(adminhtml): add × restore-to-default button for modified fields
- Add restore-button.html template (× icon, data-field-code/section-code attrs)
- badge-renderer.js: render restore button after Modified badge when isModified=true
- panel-state.js: add restoreToDefault() (fires field-restore event) and markFieldAsSaved() (single-field)
- settings-editor.js: handle field-restore — CSS preview update + saveValue auto-save + markFieldAsSaved on success
- field-handlers/base.js: handleFieldRestore() with confirm dialog + bind .bte-field-restore-btn click
- field-renderers/base.js: include .bte-field-restore-btn in badge cleanup selector
- _theme-editor-fields.less: styles for .bte-field-restore-btn (muted white, red on hover)
- dirty.html: collapse whitespace nodes to fix inline-flex rendering 65bae5 -
fix(ui): improve badge alignment — inline-flex, gap, nowrap, adjust reset button margins 3518da
-
refactor: migrate console.* to logger in remaining 16 files (part 2) 32d86b
-
fix(palette): real-time live preview for native color picker + fix palette-linked field losing var() reference
- Add 'input' event handler on .bte-swatch-input so live preview updates
while the OS color picker is open (not only on close/change)
- Remove erroneous delete changes[fieldCssVar] in _updateFieldsReferencingPalette():
the var() reference must stay in changes so CSS cascade resolves linked
fields through the updated palette var instead of falling back to the
hardcoded draft value
- Add regression tests 14-15 for the deleted-var() bug 3c1796 -
refactor(logger): migrate console.* to Logger.for() in toolbar, panel, and utility modules (part 1)
Replace all console.log/warn/error/debug calls with structured Logger.for() API
across 29 production JS files: toolbar, preview-manager, palette-manager,
field-renderers, field-handlers, and all utility/toolbar components. 62f9c4 -
fix(palette): import jquery in adminhtml palette-manager to fix reset UI update
In Magento admin jQuery.noConflict() removes window.$, so the
paletteChangesReverted event was never triggered after revertDirtyChanges().
Import jquery via AMD and remove the unreliable typeof $ guard. fc2290 -
test(palette): add palette-reset-behavior tests for focus-return click fix ba2b60
-
refactor(palette): replace console.log with BteLogger in palette-section-renderer 657764
-
fix(palette): guard against focus-return click after native color picker closes c8b0b2
-
feat(logger): add BteLogger utility with configurable log levels a54caf
-
fix(test): use namespaced event handler in selector-alignment-test 8 4e240b
-
fix(palette): prevent accordion toggle when clicking palette reset button 3b1ab5
-
fix(color): convert RGB to HEX when updating swatch on field reset
Draft values for fields with format:rgb are stored as RGB strings
(e.g. '36, 83, 182'). updateFieldUIAfterReset passed them directly to
$preview.css('background-color') and $input.val(), producing an invalid
CSS value that left the swatch showing the old palette color.
Fix: detect isRgbColor() in the non-palette branch and call rgbToHex()
before setting the input value and background-color.
Also fixes popup-instance lookup: it is stored on $trigger, not $field.
Applies to both adminhtml and frontend handlers. 093506 -
fix(adminhtml): clean up injected palette vars on field reset/overwrite
Track which palette HEX+RGB vars were injected per field variable via
_fieldPaletteVars map. Call _cleanupFieldPaletteVars() before every
setVariable/removeVariable/resetVariable so palette vars are removed when
a field switches away from a palette reference (reset, manual HEX input,
or palette-A → palette-B). Shared palette vars are preserved while any
other field still references them.
Also clears _fieldPaletteVars on full reset().
Adds css-preview-manager-palette-test.js (13 tests) covering both the
injection algorithm and the cleanup logic. e9d4c6 -
fix(test): update stale assertion in selector-alignment-test
The event trigger was changed from 'test-status' to 'DRAFT' but the
assertEquals on the same line still expected 'test-status', causing
the test to fail. Align assertion with trigger data: status 'DRAFT'
and publicationId null. 5b1f98 -
chore(phpunit): migrate config schema from 9.5 to 10.5
phpunit --migrate-configuration updated the schema URL and renamed
<coverage><include> to <source><include> per PHPUnit 10 spec.
Eliminates the 'deprecated schema' warning that appeared on every run. 004df3 -
fix(adminhtml): inject palette HEX+RGB vars in CssPreviewManager.setVariable()
Bug: when a field with format:rgb uses a palette reference (e.g.
--color-brand-amber-primary) as its value, setVariable() wrote
var(--color-brand-amber-primary-rgb) to the live preview but never
defined --color-brand-amber-primary-rgb, causing black rendering.
Fix: after formatting the field value, look up the palette HEX via the
cached _paletteManager and inject both the HEX var and its -rgb variant
into the changes map (without overwriting values already present).
Also adds css-preview-manager-palette-test.js (11 tests):
- Layer 1 (tests 1–5): _formatColorValue() pure logic
- Layer 2 (tests 6–10): injection algorithm with mock PaletteManager
- Layer 3 (test 11): integration via real setVariable() in DRAFT mode f84ed8 -
fix(adminhtml): guard invalid publicationStatusChanged enum + fix test event data
- settings-editor.js: ignore unknown status values in publicationStatusChanged
handler to prevent stale/test events triggering GraphQL requests
- publication-events-alignment-test.js: use DRAFT/null instead of PUBLICATION/9
to avoid real GraphQL network call during test run
- selector-alignment-test.js: use DRAFT instead of 'test-status' invalid enum 944a50 -
docs(phase-4): update test plan to reflect actual state
- correct JS file inventory (35 spec-files, real names)
- correct PHP count (23 files / 232 methods)
- remove Step 2 migration (PHP coverage already exists)
- add Category B/C classification for all 35 JS files
- reduce estimated time 8-10h → 5-6h 29238b -
feat(adminhtml): parallel init load, modified count, fix selector alignment tests
- publication-selector: load publications + draft metadata in parallel (_loadInitialData)
to avoid race condition on init render
- panel-state: add getModifiedCount() for fields with isModified=true
- settings-editor: pass draftChangesCount in themeEditorDraftSaved event
- metadata-loader: add loadMetadata() via GraphQL getConfig(DRAFT)
- status-indicator: sync changes count via themeEditorDraftSaved event
- selector-alignment-test: fix wrong toolbar class assertion (bte-toolbar),
fix navigation widget data key (swissupBreezeNavigation), remove stale
require(settings-editor) wrapper that caused 6s timeout 7567c5 -
fix(css-generator): emit palette RGB for vars referenced by fields but absent from DB
Root cause of Bug 3: when a field value references a palette variable
(e.g. '--color-brand-amber-dark') but the user never explicitly changed
that palette color, there is no DB entry for it. The previous code only
iterated over DB entries, so --color-brand-amber-dark-rgb was never emitted.
The Breeze base CSS defines the HEX variant but not the -rgb one, leaving
any field that uses var(--color-brand-amber-dark-rgb) with an undefined
value and broken colors.
Fix: introduce buildPaletteVarsToEmit() which
1. collects all _palette rows from DB,
2. scans non-palette field values for '--' references, and for each
referenced palette var that is absent from DB, falls back to the
default value from the theme config (via extractPaletteDefaults()).
Both generate() and generateFromValuesMap() now use this helper. Add
tests 16-17 covering the 'not in DB, use config default' scenario. 45edde -
fix(css-generator): always emit palette RGB variables even when value equals default
The Breeze base CSS defines HEX palette variables (--color-brand-*) but does
not define their -rgb counterparts (--color-brand-*-rgb). The previous code
skipped generating palette CSS when the stored value matched the palette
default, assuming Breeze base styles covered it. This caused any field
referencing var(--color-brand-*-rgb) to resolve to an empty value when the
palette color was at its default, producing broken (black) colors.
Remove the skip-if-equals-default guard in processPaletteColor() and also
remove the now-unused extractPaletteDefaults() method and its call sites.
Add tests 14 and 15 covering both generate() and generateFromValuesMap(). c403af -
fix(adminhtml): keep published CSS enabled in draft and publication modes
bte-theme-css-variables (published delta) was incorrectly disabled when
switching to DRAFT or PUBLICATION mode. Since all CSS is delta-based, the
published style must remain enabled as the base layer. Draft and publication
styles are inserted after it in the DOM, so CSS cascade naturally gives them
priority without needing to disable the published style. 9d27d0 -
feat(adminhtml): hide preset selector — not yet implemented 75a82c
-
test(adminhtml): add color-utils tests for Bug 0 fix verification
Created 12 comprehensive tests for the new adminhtml color-utils.js:
- rgbToHex() conversion with various RGB formats (plain, rgb(),
rgba(), with/without spaces, value clamping)
- isRgbColor() detection for RGB vs HEX vs invalid formats
- Bug 0 reproduction test verifying exact GraphQL scenario
("228, 2, 2" → "#e40202")
Test coverage:
- Standard RGB format: "228, 2, 2" → "#e40202"
- No spaces: "228,2,2" → "#e40202"
- Extra whitespace: " 228 , 2 , 2 " → "#e40202"
- rgb() wrapper: "rgb(17, 24, 39)" → "#111827"
- rgba() wrapper: "rgba(228, 2, 2, 0.5)" → "#e40202" (strip alpha)
- Value clamping: "300,300,300" → "#ffffff", "-10,-20,-30" → "#000000"
- Invalid formats → "#000000" fallback
- isRgbColor() detection: RGB formats return true, HEX/invalid return false
- Integration test reproducing Bug 0 fix scenario
Registered new test suite in Block/TestRunner::getAdminTestModules()
Expected new test count: 75 → 87 (12 new tests) 9b6c29 -
fix(adminhtml): convert RGB color values to HEX in color picker
GraphQL returns RGB format (e.g., "228, 2, 2") for fields with
format: rgb, but the color field renderer only accepted HEX format
(#rrggbb regex). RGB values fell through to #000000 fallback.
Changes:
- Created view/adminhtml/web/js/editor/utils/core/color-utils.js
with isRgbColor() and rgbToHex() utilities (minimal version
adapted from frontend color-utils.js)
- Updated color.js field renderer to detect and convert RGB
values (both field.value and data.default) to HEX before
display
- Handles multiple RGB formats: "228, 2, 2", "228,2,2",
"rgb(228, 2, 2)", etc.
Now color picker correctly displays RGB values from GraphQL
instead of showing black (#000000).
Resolves color picker showing #000000 for RGB format colors. 798c47 -
fix(adminhtml): restore draft CSS on page load and iframe reload
Previously _restoreCssState() only handled PUBLICATION and PUBLISHED
statuses, leaving DRAFT to fall through without calling
cssManager.switchTo('DRAFT'). This caused draft CSS to not load
on page load or after iframe navigation.
Now DRAFT status is explicitly handled, ensuring draft CSS is
loaded via GetThemeEditorCss GraphQL query on initialization
and after every iframe reload.
Fixes CSS not loading for draft mode on page load. bd8c3c -
docs: update implementation plan with completion status and success criteria a79eb6
-
style: remove trailing whitespace from blank lines per PSR-12 f4d37d
-
test: add comprehensive color conversion tests (Phase 3 & 5)
Phase 5: Enhanced ConfigTest with 2 new tests
- testConvertsColorValuesToRgbFormat: Uses REAL ColorFormatter to test
actual HEX (#000000) → RGB (0, 0, 0) conversion in resolver pipeline
- testPreservesNonColorFields: Verifies non-color fields unchanged
Phase 3: Created functional integration tests
- New file: AbstractConfigResolverColorConversionTest.php (8 tests)
- Tests full end-to-end color conversion using REAL utilities:
* ColorConverter (low-level HEX↔RGB)
* ColorFormatter (high-level formatting)
* ColorFormatResolver (format detection)
- Mocks only external dependencies (DB, providers)
Test Coverage:
- HEX → RGB conversion (black, white, colorful, short format)
- HEX preservation when format="hex"
- Palette references preserved unchanged
- CSS var() wrappers preserved unchanged
- Null value handling
Results:
- Tests: 286 (was 276, +10 new tests)
- Assertions: 900 (was 876, +24 new assertions)
- All tests passing, no regressions 8ad73e -
docs: update dashboard and add color format conversion documentation
- Mark Color Formatter implementation as completed
- Add comprehensive color-format-conversion.md guide
- Include manual testing instructions
- Document architecture and troubleshooting 64039d -
fix(graphql): convert HEX colors to RGB format when field specifies format: "rgb"
Previously, GraphQL would return null for color fields configured with
format: "rgb" because stored HEX values weren't being converted.
Changes:
- Add ColorFormatter utility for GraphQL color format conversion
- Update AbstractConfigResolver to apply format conversion to values
- Add ColorFormatter dependency to Config/ConfigFromPublication resolvers
- Add 16 unit tests for ColorFormatter (all edge cases covered)
Technical Details:
- Database stores all colors as HEX (#000000)
- Field config specifies output format (rgb/hex)
- ColorFormatter converts HEX→RGB when format="rgb"
- Palette references (--color-*) preserved unchanged
- CSS var() wrappers preserved unchanged
Test Coverage:
- All 276 tests passing (876 assertions)
- New: 16 ColorFormatter unit tests
- No regressions in existing tests
Fixes: Breeze 2.0 themes now receive proper RGB color values
(e.g., #000000 → "0, 0, 0" when format: "rgb") 1f03dd -
refactor(css): extract color picker styles to separate file and improve specificity
- Extract Pickr color picker styles from _theme-editor-fields.less to new _color-picker.less file
- Remove !important flags and use proper CSS specificity with [data-theme="nano"] attribute selector
- Add proper focus states with box-shadow matching original Pickr design
- Improve .pcr-interaction layout with > * selector for consistent margins
- Reduce _theme-editor-fields.less from ~790 to 557 lines by extracting 296 lines
- Apply identical changes to both adminhtml and frontend versions
This improves code organization and ensures consistent Pickr appearance across admin and frontend. b7d0bb -
docs: finalize Phase 3A with Hybrid Approach decision and create Phase 4 plan
Phase 3A Analysis & Decision:
- Conducted full audit of Phase 3A implementation
- DECISION: Accept Hybrid Approach as correct architecture
- GraphQL for business data (Settings, Publications, Config)
- localStorage for UI state (Device width, Toolbar visibility)
- Updated Phase 3A status: 60% → 100% complete
- Removed obsolete phase-3a-completion directory
Documentation Updates:
- DASHBOARD.md: Updated progress to 78%, reflect Hybrid Approach
- README.md: Updated quick links and project status
- AUDIT-SUMMARY.md: Added final decision with technical rationale
- migration/README.md: Updated phase status and next steps
- phase-3a/README.md: Added Architecture Decision section
- phase-3a/AUDIT-REPORT.md: Updated with final resolution
Phase 4 Planning (Test Migration & Validation):
- Created comprehensive 8-10h plan for test migration
- phase-4/README.md: Detailed implementation plan
- phase-4/test-migration-plan.md: Step-by-step migration roadmap
- Inventory of all 36 JS tests
- Migration strategy (Category A/B/C)
- PHP test templates and examples
- Success metrics and execution timeline
Project Status:
- Overall: 78% complete (92h / 118h)
- Phases 1-3B: Complete (4/5 phases done)
- Phase 4: Ready to execute (8-10h remaining)
- Phase 5: Planned (8-10h remaining) 8ee675 -
fix(admin): add Load More button visibility control for publications
- Add updateLoadMoreButton() method to renderer to hide/show button based on loaded count
- Display 'Showing X of Y' counter when more publications are available
- Show 'All publications loaded' message when all items are loaded
- Hide Load More button by default and show only when needed
- Add totalPublications to widget state for accurate tracking
Fixes issue where Load More button was always visible even when all publications were already loaded (less than 10 items). a7e567 -
docs: mark all checklist items as completed in admin-frontend-alignment plan
- Update all 5 phases (Етап 0-5) with completed checkboxes
- Add ✅ markers to all phase headers
- All 47 checklist items now marked as [x] completed
- Reflects actual completion status (84/84 tests passing) ed3480 -
docs: update admin-frontend-alignment plan with completion status
- Mark all phases as completed (Phases 0-4)
- Add critical fixes documentation (Pickr CSS + field editability)
- Add test results: 84/84 tests passing (100%)
- Document 2 commits with detailed changes
- Update timestamps and execution time (4 hours actual vs 2 hours planned) cfeb84 -
test(admin): add critical fixes tests and improve test framework
NEW TESTS: Critical Fixes Validation (4 tests)
- Test 1: Pickr CSS loading validation
- Test 2: Field editability logic (DRAFT/PUBLISHED/PUBLICATION modes)
- Test 3: DRAFT→PUBLISHED switching via publicationStatusChanged event
- Test 4: PUBLISHED→DRAFT switching via publicationStatusChanged event
- Files: view/adminhtml/web/js/test/tests/critical-fixes-test.js (NEW)
TEST FRAMEWORK IMPROVEMENTS:
- Added assertNull() and assertNotNull() assertion methods
- Files: view/adminhtml/web/js/test/test-framework.js
TEST FIXES:
- Fixed selector-alignment-test.js RequireJS syntax (IIFE → define())
- Updated all panel tests to use new selectors:
* #bte-navigation → #toolbar-navigation
* #bte-panels → #bte-panels-container
- Files: view/adminhtml/web/js/test/tests/selector-alignment-test.js
view/adminhtml/web/js/test/tests/panel-positioning-test.js
view/adminhtml/web/js/test/tests/navigation-widget-test.js
view/adminhtml/web/js/test/tests/panel-events-test.js
view/adminhtml/web/js/test/tests/panel-integration-test.js
view/adminhtml/web/js/test/tests/panel-close-integration-test.js
TEST REGISTRATION:
- Registered critical-fixes-test in admin test suite
- Files: Block/TestRunner.php
Test Results: 84/84 tests passing (100%) ✓ 34b959 -
fix(admin): fix Pickr CSS loading and field editability race condition
PROBLEM 1: Pickr CSS Missing
- Color picker rendered but had no styles (invisible/broken UI)
- Root cause: pickr-nano.min.css was not imported in admin _module.less
- Fix: Added @import (less) '../lib/pickr-nano.min.css' to admin _module.less
- Files: view/adminhtml/web/css/lib/pickr-nano.min.css (NEW)
view/adminhtml/web/css/source/_module.less
PROBLEM 2: Field Editability Race Condition
- On initial load, fields were disabled even in DRAFT mode
- Root cause: _updateFieldsEditability() called CssManager.isEditable()
which returned false due to async initialization
- Fix: Changed to synchronous local status check: isEditable = (status === 'DRAFT')
- Files: view/adminhtml/web/js/editor/panel/settings-editor.js:426-430
ADDITIONAL FIXES:
- Fixed RequireJS Pickr path: pickr.min.js → pickr.min (prevent double .js.js)
- Files: view/adminhtml/requirejs-config.js, view/frontend/requirejs-config.js
- Added CSS Manager listener for publicationStatusChanged event
- Files: view/adminhtml/web/js/editor/panel/css-manager.js
Test Results: 84/84 tests passing (100%)
Manual Testing: Color picker now has proper styles ✓
Fields editable on initial DRAFT load ✓
Fields disable/enable when switching modes ✓ cce8fc -
test(admin): add comprehensive tests for admin-frontend alignment
- Add publication events alignment tests (6 tests)
- Verify publicationStatusChanged event name
- Test orphan event removal (publicationLoaded)
- Validate event data format (object vs array)
- Test Settings Editor receives events
- Test multiple listeners (broadcast)
- Add selector alignment tests (8 tests)
- Verify #breeze-theme-editor-toolbar (not #bte-toolbar)
- Verify #toolbar-navigation (not #bte-navigation)
- Verify #bte-panels-container
- Test Constants.js values match DOM
- Test Settings Editor finds navigation correctly
- Verify no dual selectors remain
- Test Toolbar.js uses correct selectors
- Test publication events work with aligned architecture
- Register new tests in TestRunner.php
Total: 14 new tests ensuring Admin-Frontend architecture consistency daf3a0 -
refactor(admin): align with frontend architecture + fix publication events
🔴 CRITICAL FIX:
- Fix publication switching bug: Settings Editor now reloads when changing publications
- Sync events: bte:statusChanged → publicationStatusChanged (Admin = Frontend)
- Remove orphan event: publicationLoaded (no listeners)
🏗️ ARCHITECTURE ALIGNMENT:
- Rename toolbar: #bte-toolbar → #breeze-theme-editor-toolbar
- Rename navigation: #bte-navigation → #toolbar-navigation
- Update constants.js with correct selectors
- Simplify settings-editor: remove dual selector workaround
FILES CHANGED:
- view/adminhtml/templates/editor/index.phtml
- view/adminhtml/web/template/editor/toolbar.html
- view/adminhtml/web/js/editor/constants.js (TOOLBAR, NAVIGATION)
- view/adminhtml/web/js/editor/toolbar.js (selectors + event listener)
- view/adminhtml/web/js/editor/toolbar/publication-selector.js (events)
- view/adminhtml/web/js/editor/panel/settings-editor.js (selector)
BEFORE:
- Admin had different naming (#bte-toolbar, #bte-navigation)
- Events not synced (bte:statusChanged vs publicationStatusChanged)
- Settings Editor didn't reload on publication switch ❌
- Dual selector workaround needed
AFTER:
- Aligned with frontend architecture ✅
- Events synced: publicationStatusChanged everywhere ✅
- Settings Editor reloads correctly ✅
- Clean selectors (no workarounds) ✅
TESTED:
- Events properly aligned
- Selectors updated throughout
- Ready for manual testing
Files changed: 6
Lines: +18 / -14 (net +4)
Related: admin-frontend-alignment refactoring plan
Phase: ЕТАП 0-3 completed 080edc -
docs: add comprehensive audit summary for admin-frontend alignment
Created executive summary document with complete audit findings:
AUDIT RESULTS:
- 17 custom events analyzed
- 2 critical issues found
- 1 orphan event to remove
- 12 events working correctly
- ~40 JS files reviewed
CRITICAL BUGS IDENTIFIED:
1. Settings Editor not reloading on publication switch
→ Event mismatch: bte:statusChanged vs publicationStatusChanged
→ Fix time: 15 minutes (ЕТАП 0)
2. Navigation selector inconsistency
→ Dual selector workaround needed
→ Fix time: included in full alignment (2-2.5 hours)
STATISTICS:
- Files to change: 9
- Lines to change: ~20
- Total fix time: 2-2.5 hours
- Events synced: 12/17 ✅
- Selectors synced: 80%+ ✅
RECOMMENDATIONS:
- Option A: Quick fix (15 min) - fixes main bug only
- Option B: Full alignment (2-2.5 hrs) - recommended for code quality
This summary provides executive overview for decision making. 7d19d7 -
docs: update admin-frontend-alignment plan with comprehensive audit findings
Added comprehensive analysis of Admin vs Frontend inconsistencies:
🔍 AUDIT FINDINGS (Feb 18, 2026):
CRITICAL ISSUES:
- Event mismatch: bte:statusChanged vs publicationStatusChanged
→ Causes Settings Editor to NOT reload when switching publications
- Selector mismatches: #bte-navigation vs #toolbar-navigation
→ Requires fragile dual-selector workaround
COMPREHENSIVE EVENT TABLE:
- 17 events analyzed across Admin/Frontend
- Identified 2 critical fixes needed
- Documented 3 Admin-specific events (OK)
- Found 1 orphan event to remove
NEW PLAN STRUCTURE:
- Added ЕТАП 0 (15 min): Critical event fix - fixes main bug
- Updated ЕТАП 1 (25 min): Container + navigation selectors
- Total time: 2-2.5 hours (was 1.5-2)
- Files to change: 9 (was 6)
DETAILED TABLES:
- Full event comparison (Admin vs Frontend)
- Selector mismatches with file:line references
- Before/after comparisons for all changes
- Updated checklist with new critical fixes
This audit provides complete roadmap for 100% Admin/Frontend alignment. d87296 -
Fix panel close using navigation widget API
Replaces hardcoded DOM selector clicks with proper widget API calls.
This ensures close button works correctly in both admin and frontend.
## Problem
- Close button used hardcoded selector: $('#toolbar-navigation .nav-item[data-id="theme-editor"]').click()
- Admin uses #bte-navigation, Frontend uses #toolbar-navigation
- Hardcoded selector only worked in frontend, failed in admin
- Architecture violation: bypassed widget API
## Solution: Widget API (Proper Architecture)
### Changes to settings-editor.js (Admin + Frontend):
1. **Store navigation reference** in _create():
```javascript
// Supports both admin and frontend navigation IDs
this.$navigation = $('#bte-navigation, #toolbar-navigation');
```
2. **Use widget API** in _close():
```javascript
var navigationWidget = this.$navigation.data('swissupBreezeNavigation');
navigationWidget.deactivate('theme-editor');
```
### Benefits:
✅ Works in admin AND frontend
✅ Uses proper widget API (not DOM manipulation)
✅ Single reference lookup (stored in _create)
✅ Graceful error handling if navigation not found
✅ Follows architectural best practices
## Testing
### Integration Tests (5 new tests):
Created: view/adminhtml/web/js/test/tests/panel-close-integration-test.js
Tests verify:
1. Settings editor stores navigation reference
2. Close button calls navigation.deactivate()
3. navigation.deactivate() closes panel
4. Navigation button loses active class
5. Graceful handling of missing navigation
Run tests:
http://magento.local/admin/breeze_editor/editor/index?jstest=1&autorun=1
### Manual Testing:
1. Open Admin → Breeze Theme Editor
2. Click "Theme Editor" button
3. Click close (×) button
4. Console shows: "✅ Panel closed via navigation.deactivate()"
5. Panel slides out and navigation button deactivates
## Files Changed (4):
- view/adminhtml/web/js/editor/panel/settings-editor.js
- view/frontend/web/js/theme-editor/settings-editor.js
- view/adminhtml/web/js/test/tests/panel-close-integration-test.js (NEW)
- Block/TestRunner.php (register new test)
## Architecture Impact:
This sets proper pattern for all panel widgets:
- Store navigation reference once
- Use widget API for all navigation actions
- No direct DOM manipulation of navigation buttons c2261e -
Align admin panel architecture with frontend (100% consistency)
This refactoring brings the admin theme editor to complete architectural
alignment with the frontend implementation, ensuring consistency and
long-term maintainability.
## Changes Made
### 1. Container Renaming (#bte-panels → #bte-panels-container)
- constants.js: Updated PANELS selector
- index.phtml: Renamed container div ID
- toolbar.js: Updated panelSelector reference
- _panels.less: Updated CSS selector and comment
### 2. HTML Structure Cleanup
- index.phtml: Made panels empty (no headers/wrappers)
- Widgets now render ALL HTML including headers and close buttons
- Eliminates duplicate HTML issue
### 3. Widget Renaming (breezeSettingsEditor → themeSettingsEditor)
- settings-editor.js: Renamed widget declaration and return statement
- toolbar.js: Updated widget method call
- Now matches frontend widget name exactly
### 4. Lazy Loading Implementation
- navigation.js: Added panelWidgets option
- navigation.js: Added _initializePanel() method (lazy loading)
- navigation.js: Updated _showPanel() to call _initializePanel()
- toolbar.js: Moved initialization config to navigation.panelWidgets
- toolbar.js: Removed eager settings-editor initialization (lines 99-116)
## Benefits
✅ 100% Frontend/Admin consistency
✅ Easier maintenance (single pattern)
✅ Faster startup (lazy initialization)
✅ Easy to add new panels (just config)
✅ Close button now works (widget handles it)
## Testing
Cache cleared:
- pub/static/adminhtml/Magento/backend/*/Swissup_BreezeThemeEditor
- var/view_preprocessed/css/adminhtml
- All Magento cache types
Test checklist:
1. Open Admin → Breeze Theme Editor
2. Click "Theme Editor" button
3. Check console: "✅ Panel initialized: theme-editor → themeSettingsEditor"
4. Verify panel slides in from left
5. Verify close button (×) works
6. Verify panel reopens without re-initialization
## Documentation
Complete documentation added:
- docs/refactoring/admin-frontend-alignment/README.md
- docs/refactoring/admin-frontend-alignment/plan.md
- docs/refactoring/admin-frontend-alignment/test-examples.md
## Related
Closes architectural inconsistencies identified in:
- navigation-panel-integration/Phase 1 (HTML structure)
- navigation-panel-integration/Phase 2 (CSS positioning)
Files changed: 6
Lines changed: +76, -52 3c47b5 -
docs: add Phase 3 completion summary to navigation panel integration plan
Updated plan.md with comprehensive Phase 3 results:
Header Updates:
- Changed status from 'Phase 2 needs execution' to 'ALL PHASES COMPLETE'
- Updated execution times: Phase 1 (40min), Phase 2 (30min), Phase 3 (3h)
- Total project time: ~4 hours
Phase 3 Summary Section:
✅ Test infrastructure created (test-framework.js +167 lines, 5 helpers)
✅ 4 test files created with 20 tests total:
- panel-positioning-test.js (7 tests, 298 lines)
- navigation-widget-test.js (6 tests, 290 lines)
- panel-events-test.js (4 tests, 200 lines)
- panel-integration-test.js (3 tests, 184 lines)
✅ Bug fixes during testing:
- navigation.js show/hide sequence (proper timing)
- test-runner.phtml positioning (below toolbar)
- Test timing adjustments (50ms open, 350ms close)
✅ Test results: 73/73 passed (100%)
✅ Code changes: 8 files, +1166 lines
Project Status:
- Phase 1 (HTML Integration) ✅ Complete
- Phase 2 (CSS Fix LEFT positioning) ✅ Complete
- Phase 3 (JS Test Coverage) ✅ Complete
Navigation Panel Integration project fully completed with 100% test coverage.
File: docs/refactoring/navigation-panel-integration/plan.md
Lines: 1731 → 2028 (+297 lines)
Commit: e1ab62e (Phase 3 implementation) 3a8b18 -
refactor(test-runner): extract inline styles to LESS and fix toolbar overlap
- Fix test runner positioning to not overlap with bte-toolbar (top: 56px)
- Extract all inline styles from test-runner.phtml to dedicated LESS file
- Add test runner variables to _variables.less for easy customization
- Replace inline hover handlers with CSS :hover pseudo-class
- Replace style.display with classList.add('hidden') for better semantics
- Add responsive behavior: collapses to vertical strip on small screens with 'Not for small screens' message
- Improve maintainability by using semantic CSS classes instead of inline styles
Files changed:
- view/adminhtml/templates/admin/test-runner.phtml: Replace inline styles with classes
- view/adminhtml/web/css/source/components/_test-runner.less: New LESS file with all styles
- view/adminhtml/web/css/source/_variables.less: Add test runner variables
- view/adminhtml/web/css/source/_module.less: Import new test runner styles 016dbd -
feat(tests): add Phase 3 JS tests for navigation panel integration (20 tests, 100% pass)
Phase 3: Complete test coverage for navigation panel LEFT positioning
Created 4 test suites with 20 tests validating Phase 2 CSS refactoring:
- panel-positioning-test.js (7 tests) - Validates LEFT positioning, transform animation
- navigation-widget-test.js (6 tests) - Tests widget API (setActive, deactivate, toggle)
- panel-events-test.js (4 tests) - Tests event system (navigationChanged, panelShown, panelHidden)
- panel-integration-test.js (3 tests) - Tests integration, state persistence, multiple cycles
Test Framework Enhancements:
- Added 5 helper methods to test-framework.js:
* openPanel(itemId, callback) - Open admin panel via navigation
* closePanel(itemId, callback) - Close admin panel
* isPanelOpen(itemId) - Check if panel is currently open
* getTransitionDuration($element) - Get CSS transition duration
* waitForTransition($element, callback) - Wait for CSS animation completion
Test Coverage (validates Phase 2 CSS changes):
✅ Panel positioned LEFT (left: 0, not right)
✅ Transform-based animation (translateX, not margin/width)
✅ GPU-accelerated smooth animation (~300ms)
✅ Body class management (bte-panel-active)
✅ Preview margin shift (margin-left: 360px when panel open)
✅ Responsive behavior (360px desktop, 100vw mobile)
✅ Navigation widget API (setActive, deactivate, toggle states)
✅ Event system (navigationChanged, panelShown, panelHidden)
✅ Silent mode (events can be suppressed)
✅ Settings Editor integration
✅ Multiple open/close cycles
✅ State persistence during interactions
Bug Fixes (discovered during testing):
- Fixed navigation.js panel show/hide sequence:
* Opening: .show() → force reflow → setTimeout → .addClass('active')
* Closing: .removeClass('active') → setTimeout(300ms) → .hide()
* Ensures smooth transform animation before display:none
- Fixed test-runner.phtml positioning (top: 56px, below toolbar)
- Fixed panel-positioning-test.js transform check (open→close→check timing)
- Fixed panel-integration-test.js timeouts (50ms open, 350ms close)
Test Registration:
- Updated Block/TestRunner.php with 4 new test modules
- Tests auto-run when ?jstest=true parameter present
- All 73 tests pass (20 new + 53 existing = 100% pass rate)
Results: 73/73 tests passed (100%)
Browser tested: Chrome/Firefox
Animation: Smooth LEFT slide (transform-based, GPU-accelerated)
Completes Phase 3 of navigation-panel-integration refactoring.
Phase 1 (HTML) ✅ + Phase 2 (CSS) ✅ + Phase 3 (Tests) ✅ = Integration complete e1ab62 -
docs: add Phase 3 (JS Tests) plan to navigation panel integration
- Add comprehensive Phase 3 plan (20 JS tests for navigation)
- Update README.md with Phase 3 status and quick start guide
- Document test structure: 4 files with 20 tests total
- Add helper methods specification for test-framework.js
- Update project statistics (3-4 hours total, 100% coverage)
Phase 3 coverage:
- panel-positioning-test.js (7 tests) - CSS positioning validation
- navigation-widget-test.js (6 tests) - Widget functionality
- panel-events-test.js (4 tests) - Event system
- panel-integration-test.js (3 tests) - Integration tests
Test areas:
- LEFT positioning (not RIGHT)
- Transform-based animation
- Body class management (bte-panel-active)
- Preview margin shift
- Responsive behavior (mobile/desktop)
- Animation timing (300ms)
- Settings Editor integration
Status: Phase 1 & 2 ✅ Complete → Phase 3 ⏳ Ready to execute 39cf4e -
refactor(css): fix panel positioning - move from right to left
- Create separate panels/_panels.less (47 lines) for panel container styles
- Clean up _admin-preview.less (92 → 43 lines) - remove panel styles, keep only iframe preview
- Change panel position from right to left (match frontend UX)
- Use transform animation instead of position-based (GPU acceleration)
- Preview shifts via margin-left instead of width reduction
- Add responsive support: full-width panel on mobile (<768px)
Before:
- Panel appears from RIGHT
- Iframe width reduces via calc(100% - 360px)
- Styles mixed in _admin-preview.less (92 lines)
- Animation via 'right:' property (slow)
After:
- Panel appears from LEFT (matches frontend)
- Iframe shifts via margin-left: 360px
- Clean separation: _panels.less (container) + _admin-preview.less (iframe only)
- Animation via 'transform: translateX()' (GPU-accelerated, faster)
Structure:
- panels/_panels.less: #bte-panels container + .bte-panel animation
- _admin-preview.less: .bte-preview + #bte-iframe + margin shift
Files changed:
M _module.less (+1 line import)
M _admin-preview.less (-49 lines)
A panels/_panels.less (+47 lines)
Related: Phase 2 of navigation-panel-integration plan
Tested: Cache cleared, ready for browser verification c33a96 -
docs(navigation): add Phase 2 CSS fix plan to integration docs
Phase 1 (HTML Integration) completed:
- ✅ Panels HTML added to index.phtml
- ✅ Navigation works (panel opens on click)
- ⚠️ But panel appears from RIGHT instead of left
Phase 2 (CSS Fix) - NEW SECTION ADDED:
- Document CSS structural problems in admin vs frontend
- Panel positioned right instead of left
- Styles mixed in _admin-preview.less instead of separate panels/_panels.less
- Animation via 'right:' instead of 'transform:'
- Iframe width reduction instead of margin shift
Plan includes:
- Detailed frontend vs admin CSS comparison
- 5-step refactoring plan (25-30 min)
- Ready-to-use code for all files
- DevTools verification commands
- Responsive testing checklist
Files updated:
- plan.md: +400 lines (Phase 2 detailed plan)
- README.md: Updated with Phase 1 ✅ / Phase 2 ⏳ status
Next: Execute Phase 2 to fix panel positioning 0e3302 -
feat(navigation): add panel HTML markup and positioning styles
Changes:
- Add Theme Editor and Content Builder panel HTML to index.phtml
- Each panel has proper structure: header, title, close button, content area
- Update panel positioning in _admin-preview.less (360px width, slide from right)
- Add body.bte-panel-active class to shift iframe when panel opens
- Individual panels slide in/out with .active class
- Responsive styles for smaller screens (320px width)
This fixes the navigation issue where panels weren't rendering in DOM.
Navigation widget can now show/hide panels successfully.
Issue: Navigation buttons didn't show panels (panels missing in DOM)
Implementation: Variant 1 - HTML in index.phtml (simple approach) 740a67 -
docs: add navigation panel integration plan
- Create detailed step-by-step plan for adding panel HTML
- Includes 3 solution variants (recommended: HTML in index.phtml)
- Complete HTML/CSS code ready to copy-paste
- Browser testing procedures and troubleshooting guide
Issue: Navigation buttons don't show panels (panels missing in DOM)
Estimated fix time: 30-40 minutes 9383d7 -
fix: use UrlBuilder for admin URL to respect custom backend frontName
Replace hardcoded '/admin/dashboard/' with UrlBuilder to automatically
handle custom backend frontName configured in app/etc/env.php. This fixes
404 errors when admin URL is customized (e.g., /backend, /my-admin). c119a7 -
fix: update preview-manager ID consistency and mark CSS Manager refactor complete
- Fix preview-manager.js:132 to use correct ID 'bte-theme-css-variables-draft'
- Update DASHBOARD.md to reflect CSS Manager refactor completion
- Update refactoring/css-manager/plan.md status to COMPLETE
- Remove outdated documentation files (PLAN-CSS-MANAGER-REFACTOR.md, README-TESTS.md, TESTING-JSTEST-ADMIN.md) 17c957 -
docs: restructure documentation and update project status to 80% complete
- Reorganize 32+ documentation files into logical structure:
- migration/ - Admin migration project (5 phases)
- refactoring/ - Code refactoring projects
- features/ - Future feature plans
- testing/ - Testing guides and procedures
- Update project status from 43% to 80% complete:
- Phase 1 (Foundation): ✅ Complete (12h)
- Phase 2 (Security & ACL): ✅ Complete (9h)
- Phase 3A (Toolbar GraphQL): ✅ Complete (8.5h)
- Phase 3B (Settings Editor): ✅ Complete (30h)
- Phase 4 (Polish): Ready to execute (6h)
- Phase 5 (Testing): Planned (8-12h)
- Create comprehensive DASHBOARD.md with:
- Progress bars and statistics
- Quick navigation links
- Current status and next steps
- Session history
- Create README.md for each category/phase with:
- Status indicators and time estimates
- Progress tracking
- Navigation links
- Next steps
- Archive completed projects:
- Publication selector refactoring
- Admin toolbar refactoring
Total: 83.5h completed, 14-18h remaining
Next: Phase 4 (Polish & Optimization) or CSS Manager refactor 57a4ba -
fix(admin): Implement HttpGetActionInterface to bypass Secret Key validation for Index controller
- Add HttpGetActionInterface to Index controller to allow GET requests without Secret Key
- Fixes 'Invalid security or form key' error on demo server (Magento 2.4.6-p13)
- Maintains ACL protection via AbstractEditor::_isAllowed()
- Aligns with Iframe controller implementation and Magento 2 best practices 1ead0a -
feat(admin): Add automatic page-selector sync on iframe navigation
Implement automatic synchronization of the page-selector dropdown when users navigate within the iframe by clicking links. Previously, the dropdown would become stale and not reflect the actual page type being viewed.
Implementation:
- iframe-helper.js: Add detectPageTypeFromBody() to read Magento body classes (e.g. 'catalog-product-view') and convert to page type format (e.g. 'catalog_product_view')
- iframe-helper.js: Update startUrlSync() to detect page type changes and trigger 'bte:pageTypeChanged' event
- toolbar.js: Add event listener for 'bte:pageTypeChanged' to update page-selector widget
- page-selector.js: Add updateCurrentPageType() public API method to update UI without navigation
Features:
- Accurate detection using Magento's body CSS classes
- Supports all major page types (home, category, product, cart, checkout, etc.)
- No additional timers - reuses existing 500ms URL sync polling
- Triggers only when page type actually changes (optimized)
- Updates both UI and localStorage for consistency
Tests:
- Add comprehensive test suite with 10 tests covering detection, edge cases, and widget API
- All 53 tests passing (100% pass rate) 892659 -
refactor(admin): Organize utils into categorized subdirectories and merge storage helpers
- Remove duplicate storage-helper.js from panel/ directory
- Update all panel components to use editor/storage-helper.js
- Organize utils/ into logical subdirectories:
* utils/core/ - config-manager (1 file)
* utils/ui/ - error-handler, loading, permissions (3 files)
* utils/browser/ - cookie-manager, url-builder (2 files)
* utils/dom/ - color-utils, iframe-helper (2 files)
- Update all import paths across 11 files
- Update @module comments in ui utilities
Benefits:
- Single source of truth for storage helper (editor/storage-helper.js)
- Better organization and discoverability of utilities by category
- Clearer separation of concerns (core config, UI state, browser APIs, DOM manipulation)
- Easier to find and maintain related utilities ea265f -
refactor(admin): Consolidate utilities into single utils/ directory
- Remove unused auth-manager.js from adminhtml (only needed on frontend)
- Merge editor/util/ and utils/ directories into editor/utils/
- Move error-handler, loading, permissions from root utils/ to editor/utils/
- Update all import paths from editor/util/ to editor/utils/
- Update @module comments to reflect new paths
Benefits:
- Single consistent location for all editor utilities (8 files)
- Better code organization and discoverability
- Removed duplicate auth-manager.js that was unused in admin context e38b14 -
refactor(admin): replace device-frame.js with lightweight iframe-helper
Remove dead code and fix iframe access in admin area:
- Add view/adminhtml/web/js/editor/util/iframe-helper.js (88 lines)
Simple utility to access existing #bte-iframe without creating it
- Remove view/adminhtml/web/js/toolbar/device-frame.js (574 lines)
This was dead code - DeviceFrame.init() was never called in admin
DeviceFrame.getDocument() always returned null
- Update css-manager.js: toolbar/device-frame → editor/util/iframe-helper
Fix iframe document access (was broken, now works)
- Update css-preview-manager.js: toolbar/device-frame → editor/util/iframe-helper
Fix live preview style injection
- Fix color.js: #breeze-device-frame → #bte-iframe
Use correct iframe ID for admin context
Benefits:
- Remove 574 lines of unused code (-85% code size)
- Fix broken iframe access (DeviceFrame.getDocument() returned null)
- Clear separation: admin uses iframe-helper, frontend uses device-frame
- Simpler, more maintainable code 3addaf -
Migrate JS test framework to admin area with Bearer token auth
- Created admin test infrastructure in view/adminhtml/
* test-framework.js: Admin test framework
* test-runner.js: Admin test runner UI
* mock-helper.js: GraphQL mock system with Bearer token support
* admin-auth-manager-test.js: First admin test suite (8 tests)
* admin/test-runner.phtml: Admin test UI template (orange header)
- Modified Block/TestRunner.php:
* Added isAdminContext() with 3-level fallback detection
* Split test modules into getAdminTestModules() and getFrontendTestModules()
* Admin tests: 1 suite (8 tests)
* Frontend tests: 23 suites (162 tests)
- Updated layouts:
* view/adminhtml/layout/breeze_editor_editor_index.xml: Added admin test runner block
* view/frontend/layout/breeze_default.xml: Disabled frontend test runner
- Added comprehensive documentation:
* TESTING-JSTEST-ADMIN.md: Testing guide with troubleshooting
* docs/refactoring/jstest-implementation-summary.md: Full implementation guide (1,100+ lines)
* docs/refactoring/README.md: Migration status and roadmap
Key differences:
- Frontend: Custom headers auth, Native Promises, iframe-based editor
- Admin: Bearer token auth, jQuery Deferred, no iframe
Admin test URL: /admin/breeze_editor/editor/index/jstest/1
Expected result: 1 test suite loaded (admin-auth-manager-test) c33059 -
docs: update publication-selector refactoring documentation with test results
- Added FINAL-SUMMARY.md - comprehensive Ukrainian summary
- Added QUICK-REFERENCE.md - developer quick start guide
- Added TESTING-CHECKLIST.md - printable browser testing checklist
- Added completion-summary.md - complete English project overview
- Added stage3-testing.md - detailed Stage 3 testing guide
- Updated plan.md with testing results and final metrics
All 3 refactoring stages completed and verified in browser:
- Stage 2 (ba9e00a): Performance & Code Quality
- Stage 3 (f6e40b8): Modular Architecture
Testing results: All tests passed, zero errors, zero regressions.
Documentation: 10 files created (~110K total). 28bb0a -
refactor(admin): Complete Stage 3 - Modular Architecture
- Created renderer.js module (229 lines) for UI updates
- Created metadata-loader.js module (148 lines) for data loading
- Refactored main file to coordinator pattern (537 lines)
- Reduced main file by 262 lines through modularization
- Achieved modular architecture matching frontend version
The main publication-selector.js now acts as coordinator between:
- Renderer module (UI updates, templates, computed values)
- MetadataLoader module (GraphQL queries, data formatting)
- StorageHelper (localStorage persistence)
- CSSManager (CSS lifecycle management)
All modules use factory pattern with Object.create().init() for consistency. f6e40b -
refactor(admin): Complete Stage 2 - Performance & Code Quality
- Added smart update methods (updateButton, updateBadge, updateCheckmarks)
- Replaced full _render() with smart partial updates where possible
- Added computed value methods (_getDisplayLabel, _getBadgeText, _getBadgeClass, _getMetaText)
- Simplified template by using computed values
- Enhanced i18n localization throughout
- Improved performance with targeted DOM updates instead of full re-renders ba9e00 -
docs: organize refactoring documentation into proper structure
- Move REFACTORING-SUMMARY.md → docs/refactoring/publication-selector/summary.md
- Move TEST-CHECKLIST.md → docs/refactoring/publication-selector/checklist.md
- Move debug-css-manager.js → docs/refactoring/publication-selector/debug-script.js
- Add docs/refactoring/publication-selector/plan.md (46KB detailed refactoring plan)
- Add docs/refactoring/publication-selector/README.md (navigation guide)
- Add docs/README.md (main documentation index)
Structure:
docs/
├── README.md # Main documentation index
└── refactoring/
└── publication-selector/
├── README.md # Quick start guide
├── plan.md # Detailed refactoring plan (3 stages)
├── summary.md # CSS architecture refactoring context
├── checklist.md # Testing checklist
└── debug-script.js # Browser console debug tool
Benefits:
- Clean project root (no loose documentation files)
- Logical structure for future documentation
- Easy navigation with README files
- Ready for new session to continue refactoring cb9fe0 -
fix: editor/css-manager should not use DeviceFrame (different iframe)
Problem: After previous commit, CSS Manager initialization failed with:
"CSS Manager not initialized or iframe document not available"
Root Cause:
- Toolbar uses iframe #bte-iframe (already exists in DOM)
- DeviceFrame creates iframe #breeze-device-frame (for Settings Panel)
- These are TWO DIFFERENT iframes!
- editor/css-manager.js was trying to use DeviceFrame.getDocument()
which returns document for #breeze-device-frame, not #bte-iframe
- Result: getDocument() returned null → CSS Manager failed
Solution:
- Removed DeviceFrame dependency from editor/css-manager.js
- _getIframe() now uses document.getElementById(iframeId)
- _getCurrentIframeDoc() gets document from correct iframe
- Still dynamically fetches document (not cached) to handle navigation
Note: panel/css-manager.js SHOULD use DeviceFrame (it works with Settings Panel)
Files changed:
- view/adminhtml/web/js/editor/css-manager.js (-1 dependency, updated helpers) 13d04d -
fix: dynamically get iframe document to handle page navigation
Problem: After navigating to a new page in iframe, CSS styles (publications)
were not being applied. The iframe would reload with a new document, but
CSS Manager was holding a reference to the OLD document.
Root Cause:
- CSS Managers stored iframe document in a global variable on init
- After iframe navigation, the variable pointed to stale/invalid document
- Creating <style> elements in old document had no effect on new page
- Result: Only published CSS visible, publications lost
Diagnosis confirmed in console:
iframeDoc.querySelectorAll('style[id^="bte-"]')
→ NodeList [style#bte-theme-css-variables] (only 1!)
After switchTo('PUBLICATION', 9):
→ Still NodeList(1) - publication style not created in DOM
Solution:
1. DeviceFrame.getDocument() - now returns FRESH document every time
- Added getIframe() method
- getDocument() calls iframe.contentDocument dynamically
- No longer returns cached global variable
2. editor/css-manager.js:
- Removed global currentIframeDoc variable
- Added _getIframe() and _getCurrentIframeDoc() helpers
- All methods now get fresh document via DeviceFrame
- Properly creates styles in CURRENT iframe document
3. panel/css-manager.js:
- Replaced iframeDocument global with getIframeDocument() helper
- Uses DeviceFrame.getDocument() dynamically
- Removed invalid assignments (getIframeDocument() = ...)
Expected behavior after fix:
✅ Navigate to new page → CSS state restored from localStorage
✅ Publication styles created in NEW iframe document
✅ Colors persist across page navigation
Console verification:
iframeDoc.querySelectorAll('style[id^="bte-"]')
→ NodeList(2) [style#bte-theme-css-variables, style#bte-publication-css-9]
Files changed:
- view/adminhtml/web/js/toolbar/device-frame.js (+27 lines)
- view/adminhtml/web/js/editor/css-manager.js (+42, -36 lines)
- view/adminhtml/web/js/editor/panel/css-manager.js (+33, -30 lines) d7eb74 -
fix: restore CSS state on iframe navigation using event-based approach
Previously when navigating between pages in Theme Editor, the iframe would
always load DRAFT CSS regardless of the current state (PUBLICATION/PUBLISHED).
Root cause: toolbar.js iframe.load event handler called previewManager.injectDraftCSS()
without checking localStorage state.
Solution (Event-based approach):
- toolbar.js: Replaced injectDraftCSS() with bte:iframeReloaded event trigger
- publication-selector.js: Added listener for bte:iframeReloaded that calls _restoreCssState()
- Now CSS state (Draft/Published/Publication) is properly restored from localStorage on navigation
Expected console logs after fix:
🎨 Iframe loaded, triggering CSS state restoration...
📥 Iframe reloaded event received, restoring CSS state...
🔄 Restoring CSS state from localStorage...
✅ Restored PUBLICATION mode: 5
Files changed:
- view/adminhtml/web/js/editor/toolbar.js (9 lines removed, 6 lines added)
- view/adminhtml/web/js/editor/toolbar/publication-selector.js (6 lines added) 5d3a06 -
docs: add comprehensive refactoring summary
Added REFACTORING-SUMMARY.md with:
- Initial problem analysis with logs
- Detailed explanation of all changes
- Before/after comparisons
- Statistics and metrics
- Testing instructions
- Expected console logs
- Next steps and notes
This document serves as complete documentation for the CSS architecture
refactoring that fixed publication switching issues. c54c7b -
fix: add force reflow to _enableStyle methods for reliable CSS updates
- Added force reflow (offsetHeight) to all _enableStyle methods
- Ensures browser applies style changes immediately in iframe
- Fixes potential issue where styles appear enabled but don't apply visually
- Added debug script and test checklist for manual testing
Files changed:
- view/adminhtml/web/js/editor/css-manager.js
- view/adminhtml/web/js/editor/panel/css-manager.js
- view/frontend/web/js/theme-editor/css-manager.js
+ TEST-CHECKLIST.md (comprehensive testing guide)
+ debug-css-manager.js (browser console debug tool) 42255c -
refactor: simplify CSS architecture - remove PHP draft rendering
Changes:
- ViewModel: Removed getInlineCssContentDraft(), hasAccessToken(), isTestMode()
- Template: Render only PUBLISHED CSS, draft created by JS dynamically
- CSS Managers (admin/frontend/panel): Always create draft dynamically via GraphQL
- CSS Managers: Fixed _disableStyle() with force reflow for reliable iframe updates
- Device Frame: Only copy published style, draft created in iframe by JS
- Device Frame: Removed draft style from sync observer
Benefits:
- Simpler architecture: PHP only renders PUBLISHED, JS handles DRAFT
- No PHP logic for test mode / access token in templates
- Consistent behavior between admin iframe and frontend
- Draft CSS always fresh from GraphQL (no PHP cache issues) cee6a7 -
backup: before simplifying CSS architecture - removing PHP draft rendering 6b7867
-
docs: Add Phase 3 implementation plans and session progress
Documentation Added:
- PHASE-3-IMPLEMENTATION-PLAN.md - Detailed Phase 3A guide (1,070 lines)
- PHASE-3B-IMPLEMENTATION-PLAN.md - Settings Editor migration guide (1,347 lines)
- PHASE-3-QUICK-START.md - Quick reference guide
- SESSION-PROGRESS-2026-02-11.md - Previous session notes
Documentation Updates:
- admin-migration-phase-3.md - Split into 3A and 3B sub-phases
- admin-migration-plan.md - Updated timeline (38.5 hours total)
- SESSION-PROGRESS.md - Current session progress
Phase 3 Split Rationale:
Originally estimated at 8-10 hours, Phase 3 discovered to require:
- 3A: Toolbar GraphQL (8.5h) ✅ COMPLETE
- 3B: Settings Editor Migration (30h) - Next phase
Naming Decision: panel.js → settings-editor.js
Better reflects purpose (editing theme settings/variables)
Allows for future panels (selectors-panel, layout-panel)
Ready for Phase 3B execution. e84e63 -
feat(phase3a): Implement toolbar GraphQL integration and utilities
Phase 3A: Toolbar GraphQL Integration - Complete (8.5 hours work)
New Utility Files:
- utils/permissions.js - ACL permission checks and UI restrictions
- utils/error-handler.js - Centralized GraphQL error handling
- utils/loading.js - Loading states and spinners
- editor/preview-manager.js - CSS injection into preview iframe
Updated Components:
- publication-selector.js - Full GraphQL integration:
* Real publish mutation with error handling
* Load publications from GraphQL with pagination
* Permission-based UI (publish/rollback buttons)
* Loading states during async operations
* Event triggers for status changes
- status-indicator.js - Real-time status updates:
* GraphQL status refresh every 30 seconds
* Auto-refresh on save/publish events
* Dynamic draft changes count
- toolbar.js - Preview manager integration:
* Initialize preview on iframe load
* Inject draft CSS automatically
* Refresh preview on save/status change events
* Global event binding for all components
CSS Enhancements:
- _utilities.less - Loading and permission styles
- Loading spinner animations
- Permission-denied states with visual feedback
- Tooltip support for disabled elements
Features Implemented:
✅ GraphQL mutations (publish)
✅ GraphQL queries (publications, statuses, CSS)
✅ Bearer token authentication
✅ ACL permission checks in UI
✅ Error handling with user-friendly messages
✅ Loading indicators during async operations
✅ Live preview CSS injection
✅ Auto-refresh status (30s interval)
✅ Event-driven architecture (bte:saved, bte:published, bte:statusChanged)
All TODO comments from Phase 1 resolved.
Ready for Phase 3B (Settings Editor Migration).
Refs: #phase3, #toolbar, #graphql, #permissions eee5e4 -
docs: Add Phase 2 completion and Phase 3 planning documentation
Added comprehensive documentation:
ACL-TESTING-GUIDE.md (450 lines):
- Complete testing guide for validating ACL permissions
- 4 test roles with setup instructions
- Test matrix for all 20 operations × 4 roles
- GraphQL test queries
- Automated bash test script
- Troubleshooting guide
PHASE-2-COMPLETION-SUMMARY.md:
- Complete Phase 2 implementation summary
- Architecture overview
- ACL permission matrix
- All 20 operations documented
- Test results (259/259 passing)
PHASE-3-PLAN.md (380 lines):
- Detailed Phase 3 implementation plan
- 6 tasks with time estimates
- Technical decisions
- File structure
- Testing strategy
SESSION-PROGRESS.md:
- Current session summary
- Progress metrics (Phase 2: 100%, Phase 3: 40%)
- Files modified/created
- Next steps
tests/test-graphql-auth.sh:
- Automated ACL testing script
- Tests authentication and authorization
- Validates token generation
- Checks ACL enforcement f21e89 -
test(acl): Update unit tests for ACL integration
Updated resolver tests:
- ConfigTest, ValuesTest - updated to extend AbstractQueryResolver
- SaveValueTest, SavePaletteValueTest - updated for AbstractMutationResolver
- PublishTest, RollbackTest - updated with getAclResource() overrides
- ExportSettingsTest - verified ::editor_view permission
Added AdminTokenGeneratorTest:
- Test token generation for admin users
- Test token expiration (3600 seconds)
- Test error handling for missing users
- Mock UserTokenIssuer and related services
All tests passing: 259/259 (811 assertions) 8c061b -
feat(permissions): Pass ACL permissions to frontend JavaScript
Modified ViewModel/AdminToolbar:
- Inject AuthorizationInterface
- Add getPermissions() method checking all 4 ACL resources
- Include permissions in getToolbarConfig() output
Permissions available in JavaScript:
- canView (::editor_view) - read-only access
- canEdit (::editor_edit) - draft editing
- canPublish (::editor_publish) - publish to production
- canRollback (::editor_rollback) - revert publications
Access via: window.breezeThemeEditorConfig.permissions
This enables permission-based UI (hide/disable buttons based on user
ACL roles) in the frontend toolbar and editor interface. 05af3d -
feat(auth): Add JWT Bearer token authentication for admin GraphQL
Added AdminTokenGenerator service:
- Generates JWT tokens for admin users via Magento's UserTokenIssuer
- 1-hour token expiration (3600 seconds)
- Integrates with TokenManager for session storage
- Automatic token generation on admin toolbar load
Updated GraphQL client (view/adminhtml/web/js/graphql/client.js):
- Bearer token authentication (Authorization header)
- Token persistence in localStorage
- Automatic 401 handling (clear invalid tokens)
- Store header support
- Generic error messages for security
Updated TokenManager and UserResolver:
- Add admin token management methods
- Support both frontend (access token) and admin (Bearer token) flows
This replaces custom header authentication with standard JWT Bearer
tokens, fully compatible with Magento's TokenUserContext for automatic
validation and ACL integration. 38dcc9 -
refactor(acl): Update all resolvers to use ACL abstract classes
Query Resolvers (9) - extend AbstractQueryResolver:
- Config, ConfigFromPublication, Values, Compare, Statuses
- Publications, Publication, Presets, GetCss
All inherit ::editor_view permission
Mutation Resolvers (11):
- SaveValue, SaveValues, SavePaletteValue, DiscardDraft (::editor_edit)
- ApplyPreset, ResetToDefaults, CopyFromStore, ImportSettings (::editor_edit)
- Publish - override to require ::editor_publish
- Rollback - override to require ::editor_rollback
- ExportSettings - override to require ::editor_view (read-only)
All resolvers now implement getAclResource() via inheritance or override.
Abstract base classes updated to extend new AbstractQueryResolver/
AbstractMutationResolver. 0def0f -
feat(acl): Add ACL infrastructure for GraphQL resolvers
- Add ResolverInterface with getAclResource() method
- Add AbstractQueryResolver with default ::editor_view permission
- Add AbstractMutationResolver with default ::editor_edit permission
- Add AclAuthorization plugin for permission checking
- Configure plugin in etc/graphql/di.xml
This provides granular ACL control for all 20 GraphQL operations:
- 9 queries require ::editor_view
- 8 mutations require ::editor_edit
- 1 mutation requires ::editor_publish (Publish)
- 1 mutation requires ::editor_rollback (Rollback)
- 1 mutation requires ::editor_view (ExportSettings - read-only)
Plugin intercepts all resolvers, validates authentication (admin only),
checks ACL permissions, logs denied requests, and returns generic error
messages for security. ce7311 -
fix(admin): Reset page selector to home when switching stores
Added resetToHomePage() method to page-selector widget that resets the
page selection UI to homepage when store is changed via scope-selector.
Problem:
- When switching stores, iframe correctly loads homepage
- But page-selector UI still shows previously selected page (e.g. 'Category Page')
- This creates confusing UX where UI and actual page don't match
Solution:
1. Added resetToHomePage() public method to page-selector
- Resets currentPageId to 'cms_index_index'
- Updates currentPageLabel via _findPageLabel()
- Re-renders UI to show 'Home Page'
2. Updated scope-selector to call resetToHomePage() after updateStoreParam()
- Ensures page selector UI always matches actual page after store change
Benefits:
- Clear UX: page selector always shows correct current page
- Consistent state: UI reflects reality
- Better user experience when working with multiple stores 6c30fa -
refactor(admin): Clean up Phase 1 - remove redundant parameters
Removed unnecessary parameters and added missing ones for cleaner architecture:
1. Removed theme_id from Index.php block data (theme determined automatically by Observer)
2. Removed currentStoreId duplicate from toolbarConfig (use storeId instead)
3. Removed iframeBaseUrl from toolbarConfig (pageTypes have absolute URLs)
4. Added jstest parameter to toolbarConfig for proper state tracking
5. Refactored page-selector to use storeCode/jstest from config instead of parsing URL
6. Enhanced Phase 2 stub comments with implementation details
Benefits:
- Reduced config size (removed 2 redundant params)
- Single source of truth for parameters
- Clearer code intent with detailed stub comments
- Better parameter passing (explicit vs implicit) acb51e -
fix(admin): Use ViewModel->getStoreId() instead of hardcoded fallback
Replaced unsafe fallback to store ID = 1 with ViewModel->getStoreId() to ensure
iframe and toolbar config use the same store ID source, respecting URL params,
cookies, and default store priority fef967 -
refactor(admin): Improve admin toolbar URLs and UI enhancements
- Override getAdminUrl() and getGraphqlEndpoint() in AdminToolbar ViewModel
- Generate admin dashboard URL without security key
- Generate frontend GraphQL endpoint without admin prefix
- Update toolbar.js and page-selector.js with improved navigation
- Fix HTML formatting in template files (proper closing tags)
- Update documentation in ViewModel about overridden methods
These changes are from previous work on removing access tokens from URLs. f75e19 -
fix(admin): Fix iframe src attribute update and store switching
- Rewrite _selectStore() method in scope-selector.js:
* Change iframe.src attribute instead of contentWindow.location
* Use regex to replace /store/\d+/ with new store ID
* Always reset to homepage (/) when switching stores
* Remove _updateUrlStoreParam() method (no longer needed)
* Remove _setThemePreviewCookie() method (handled by Observer)
- Add cookieManager.setStoreCookie() for store cookie
- Add cookieManager.setCookie() for bte_last_store_id (24h)
- Update configManager with new store code/ID
- Add new util modules: cookie-manager, config-manager, url-builder
- Add constants.js for shared configuration
Fixes: F5 in iframe was reloading original URL instead of current page
Fixes: Store selection was not persisting across page refreshes
Fixes: Switching stores didn't update iframe src correctly 94d3e8 -
feat(admin): Remove theme from URL parameters, read from store config
- Update Iframe controller: remove theme from frontend URL generation
- Update SetThemePreviewCookie observer:
* Read theme ID from store config (design/theme/theme_id)
* Set both 'store' and 'preview_theme' cookies
* Add ScopeConfigInterface and StoreManagerInterface dependencies
* Add LoggerInterface for debugging
* Listen to ___store parameter (frontend requests)
- Update index.phtml template: remove themeId variable
- Update events.xml: improve documentation about cookies
- Add URL building logs to Iframe controller
Fixes: Theme is now determined automatically per store
Fixes: F5 in iframe shows 'Unknown Store' error
Fixes: Theme wasn't being set correctly when switching stores 0ee3f2 -
feat(admin): Implement 3-tier store selection with cookie persistence
- Add EditorSession and LoggerInterface to AbstractEditor constructor
- Rewrite getStoreId() with priority logic:
1. URL parameter ?store=X (highest priority, saves to cookie)
2. Cookie 'bte_last_store_id' (remembers last choice)
3. Default store view (fallback)
- Add logging for debugging store selection logic
- Validates store exists before returning ID
- Fixes issue: editor always loaded with admin store (ID=0) instead of frontend
Related: Controller/Adminhtml/Editor/Iframe.php will need same constructor update 79e376 -
feat(admin): Add BackendSession service for store persistence
- Create Model/Session/BackendSession.php for managing store ID
- Store/retrieve store ID from 'bte_last_store_id' cookie
- 24-hour cookie lifetime for session persistence
- Uses PSR LoggerInterface for debugging
- Methods: setStoreId() / getStoreId() (simplified naming)
- Enables 'remember last store' functionality d6207c -
fix(admin): Intercept iframe link clicks to preserve store and theme
Problem: When clicking links inside the iframe, the theme would reset to
the default store theme (Luma) instead of maintaining the selected theme.
This happened because link navigation bypassed the Iframe controller and
went directly to frontend URLs without store/theme parameters.
Solution: JavaScript link interception
Backend changes:
- Add storeCode to AdminToolbar config for JavaScript access
Frontend changes:
- Remove ineffective cookie-based approach
- Add link click interceptor that runs on every iframe load
- Intercept all <a> clicks inside iframe content
- Automatically inject ___store and preview_theme parameters
- Sync storeCode when switching stores via scope-selector
How it works:
1. On iframe load, attach click handler to all <a> tags
2. When link is clicked, check if it needs parameters
3. Add ___store and preview_theme if missing
4. Navigate with parameters, preserving theme context
5. Re-attach handler after navigation completes
Special handling:
- Skip anchor links (#section)
- Skip external links (different origin)
- Skip javascript:, mailto:, tel: protocols
- Don't duplicate parameters if already present
- Graceful error handling with fallback to default navigation
This ensures theme persistence across all navigation methods:
- Toolbar navigation (page/scope selectors) ✓
- Link clicks inside iframe ✓ (newly fixed)
- Store switching ✓ (with config sync) 2a1f85 -
fix(admin): Add store and theme parameters to iframe URL
- Add ___store parameter to frontend URL to preserve store view context
- Add preview_theme parameter to maintain selected theme on navigation
- Add iframe load event listener to refresh preview_theme cookie
- Fixes theme reset when clicking links inside iframe
This ensures the correct store view and theme are maintained when users
navigate by clicking links inside the iframe, not just when using toolbar
navigation controls. f37828 -
fix(admin): Preserve theme when navigating in iframe
Problem:
- When switching pages or clicking links in iframe, Luma theme was displayed
- Selected Breeze theme was lost on navigation
- Theme selector showed correct theme, but iframe rendered default store theme
Root cause:
- JavaScript navigation (iframe.contentWindow.location.href = newUrl)
used clean frontend URLs without theme information
- Magento rendered default theme from store configuration
- No theme context passed between navigations
Solution:
- Use Magento's preview_theme cookie mechanism (standard approach)
- Set cookie before every iframe navigation
- Cookie persists theme across all page loads and link clicks
Implementation:
1. Backend (Observer):
- Observer/SetThemePreviewCookie.php: NEW
- Sets preview_theme cookie on editor entry
- Event: controller_action_predispatch_breeze_editor_editor_index
- Cookie: preview_theme={themeId}, path=/, SameSite=Lax
2. Frontend (JavaScript):
- page-selector.js:
* Add themeId option
* Add _setThemePreviewCookie() method
* Set cookie BEFORE page navigation
- scope-selector.js:
* Add themeId option
* Add _setThemePreviewCookie() method
* Set cookie BEFORE store switch
- toolbar.js:
* Pass config.themeId to both widgets
3. Configuration:
- etc/adminhtml/events.xml: NEW
- Register SetThemePreviewCookie observer
Result:
✅ Theme persists when switching pages
✅ Theme persists when switching stores
✅ Theme persists when clicking links in iframe
✅ Uses Magento standard preview_theme cookie
✅ Works for all navigation scenarios 09b97f -
feat(admin): Remove access token from admin toolbar URLs
Admin users are already authenticated via session, so the
breeze_theme_editor_access_token parameter is unnecessary and adds
visual clutter to URLs.
Problem:
- All admin toolbar URLs contained: ?breeze_theme_editor_access_token=...
- Admin is already authenticated via Magento\Backend\Model\Auth\Session
- Token only needed for frontend toolbar (guest access)
Solution:
- Add area detection via Magento\Framework\App\State
- Add shouldAddToken() helper method in both files
- Conditionally add token only in frontend area
- Admin area: No token (session auth sufficient)
- Frontend area: Token preserved (URL-based access)
Changes:
- ViewModel/Toolbar.php:
* Inject State dependency
* Add shouldAddToken() protected method
* Update getPageSelectorData() to check area before adding token
- Model/Provider/StoreDataProvider.php:
* Inject State dependency
* Add shouldAddToken() private method
* Update getStoreUrl() to check area before adding token
- ViewModel/AdminToolbar.php:
* Pass State parameter to parent constructor
- docs/admin-migration-phase-1.md:
* Add "Token Authentication Strategy" section
* Document area-specific behavior
* Add implementation details
Result:
✅ Admin URLs: https://magento248.local/gear.html?___store=default (clean)
✅ Frontend URLs: Token preserved when implemented
✅ All navigation and store switching works correctly 7c4f21 -
feat(admin): Add area-specific PageUrlProvider for correct frontend URLs
Admin toolbar was generating admin URLs instead of frontend URLs:
- /admin/checkout/cart → /checkout/cart
- /admin/cms/page/view → /enable-cookies
Changes:
- Create AdminPageUrlProvider with FrontendUrlBuilder injection
- Use _nosid, _scope, _type params to force frontend URL generation
- Add FrontendPageUrlProvider alias for frontend area
- Configure DI preferences for both areas (adminhtml, frontend)
- Store URL parameters in page-selector widget state
- Add contentWindow navigation logic to scope-selector
Backend:
- Model/Provider/PageUrlProvider.php: Changed private → protected
- Model/Provider/AdminPageUrlProvider.php: NEW (frontend URLs from admin)
- Model/Provider/FrontendPageUrlProvider.php: NEW (alias for base)
- etc/adminhtml/di.xml: NEW (DI configuration)
- etc/frontend/di.xml: Added DI preference
Frontend:
- view/adminhtml/web/js/editor/toolbar/page-selector.js:
* Add currentParams storage for store/jstest params
* Remove iframe URL reading (avoids wrapper URLs)
* Add _initializeCurrentParams() method
- view/adminhtml/web/js/editor/toolbar/scope-selector.js:
* Add contentWindow navigation logic
* Enhance error logging 1c1d83 -
Fix: Resolve duplicate domain URLs in page and scope selectors
Critical bug fix for URL building in toolbar navigation:
**Issue:**
Page selector was creating malformed URLs with duplicate domains:
https://magento248.local/https://magento248.local/... (404 error)
**Root Cause:**
- PageUrlProvider returns absolute URLs with domain
- JavaScript was concatenating: iframeBaseUrl + pageUrl
- Result: duplicate domain in URL
**Solution:**
- page-selector.js: Parse pageUrl as absolute URL directly
- Remove iframeBaseUrl concatenation
- Preserve query params (___store, jstest, access token)
- scope-selector.js: Add better error logging
- AdminToolbar.php: Document that pageTypes contain absolute URLs
**Changes:**
- view/adminhtml/web/js/editor/toolbar/page-selector.js:179-218
- Parse pageUrl as absolute URL using URL() constructor
- Remove iframeBaseUrl concatenation
- Add access token preservation
- Better error handling with debug logging
- view/adminhtml/web/js/editor/toolbar/scope-selector.js:236-256
- Add documentation about absolute URL handling
- Enhance error logging
- ViewModel/AdminToolbar.php:306-380
- Update docblock to clarify pageTypes contain absolute URLs
- Note iframeBaseUrl kept for backward compatibility
- Add inline comments
**Testing:**
All page navigation should now work correctly without 404 errors. 3907f2 -
Feature: Complete toolbar toggle with collapse/expand and utility buttons
- Implement full toolbar hide/show with floating compact button
- Add highlight toggle button (Phase 2 functionality ready)
- Add proper exit button widget with configurable URL
- Move device switcher to right section (matches frontend layout)
- Toolbar state persists in localStorage
- Smooth animations with CSS transforms
- Compact button appears at top-right when toolbar hidden
- All utility buttons properly styled and functional
- Add exitUrl to AdminToolbar config 8065e0 -
Fix: Make scope and page selector dropdowns visible with proper styling
- Replace complex animation mixin with simple display:none/block toggle (matching publication-selector pattern)
- Add full dropdown styles directly in component files instead of using mixin
- Fix scope-store items layout: add display:flex, proper padding, borders
- Add item-check styles for active state indicator
- Update JS to toggle 'active' class on button for visual feedback
- All dropdowns now work consistently with jQuery .toggle() and .hide()
Issue: Dropdowns were not visible because:
1. Mixin used max-height animation conflicting with jQuery inline styles
2. Missing display:flex on .scope-store items causing inline layout
3. Button active state not reflected visually
Solution: Use same simple approach as publication-selector - display:none by default, jQuery handles visibility 0ae6b8 -
Fix: Add missing toolbar-select button styles for scope and page selectors
Problem:
- Scope selector and page selector buttons had no styling
- Only dropdown styles were present, but the trigger button was unstyled
- Frontend relies on global .toolbar-select styles, but admin doesn't have them
Solution:
Added .toolbar-select styles to both components:
- Display flex with proper alignment
- Padding, borders, colors from design tokens
- Hover and active states
- Arrow rotation animation on dropdown open
- Consistent with publication-selector button styles
Changes:
- _scope-selector.less: Added 52 lines of button styles
- _page-selector.less: Added 52 lines of button styles
Result:
- Scope selector button now visible and styled
- Page selector button now visible and styled
- Consistent appearance with publication selector
- Proper hover effects and active states
Refs: Phase 1B - Admin Toolbar Styling c013d6 -
Remove duplicate status indicator from right toolbar section
Changes:
- Commented out status-indicator widget initialization in toolbar.js (lines 71-77)
- Added TODO comment in toolbar.html for future Highlight Toggle button
- Kept <div id="bte-status"> container for future use
Reason:
Status indicator (Draft/Published) was duplicate of Publication Selector
in center section. Publication Selector is the primary status control.
Future use:
This space reserved for Highlight Toggle button (show/hide element highlights)
Result:
Right section now shows only Exit button until Highlight Toggle implemented.
Refs: Phase 1B - Admin Toolbar Styling ff1842 -
Fix: Use unescaped output for SVG icons in navigation
Changed <%- item.icon %> to <%= item.icon %> in navigation template.
Problem: SVG was HTML-escaped (<svg> instead of <svg>)
Solution: Use <%= %> for unescaped HTML output instead of <%- %>
Underscore.js template syntax:
- <%= ... %> - outputs raw HTML (unescaped)
- <%- ... %> - outputs escaped HTML (safe for text)
Icons now render as proper SVG instead of encoded text.
Refs: Phase 1B - Admin Toolbar Styling 0bacf6 -
Fix: Remove duplicate title and use dynamic icons in navigation
Changes in navigation.html:
- Replaced hardcoded placeholder icon (9 circles) with dynamic <%- item.icon %>
- Now renders actual SVG icons from PHP config (pencil for Theme Editor, grid for Content Builder)
Changes in toolbar.html:
- Removed duplicate <h1 class="bte-title">Theme Editor</h1> heading
- This element caused double appearance of "Theme Editor" text
- Navigation buttons now sole source of navigation labels
Result:
- Left section: Admin link → divider → Navigation (Theme Editor + Content Builder)
- No more duplicate "Theme Editor" text
- Correct icons (pencil + grid) instead of placeholder circles
Refs: Phase 1B - Admin Toolbar Styling 72dd47 -
Fix: Replace placeholder navigation with correct Theme Editor buttons
Changed navigation items from placeholder data to real buttons:
- Theme Editor (pencil icon) - active button
- Content Builder (grid icon) - disabled with PRO badge
Before: 3 identical placeholder buttons (Theme, Layout, CSS)
After: 2 proper buttons matching frontend implementation
Includes full SVG icons inline for proper rendering.
Refs: Phase 1B - Admin Toolbar Styling b82701 -
Wire up component styles in module imports
- Updated _module.less to import 3 new component files
- Added imports after toolbar components section
- Publication selector: status-based styling (draft/published/historical)
- Scope selector: hierarchical dropdown with collapsible sections
- Page selector: simple page type dropdown
- All use existing variables and .bte-toolbar-dropdown() mixin
- Zero template or JavaScript changes required
Component features:
• Publication: colored borders, recent publications list, publish button
• Scope: 3-level hierarchy (Website→Group→Store), toggle icons
• Page: active highlighting, checkmarks for current selection
• All: smooth animations, custom scrollbars, responsive design
Refs: Phase 1B - Admin Toolbar Styling a27973 -
Create components directory with selector styles
- Created view/adminhtml/web/css/source/components/ directory
- Added _publication-selector.less (430 lines) - status colors, dropdown
- Added _scope-selector.less (147 lines) - hierarchical store selector
- Added _page-selector.less (14 lines) - simple page type selector
- Copied from frontend with root class adaptations (.toolbar-* → .bte-*)
- Structure mirrors frontend components/ organization
Refs: Phase 1B - Admin Toolbar Styling 2ec20d -
Docs: Add refactoring summary
- Detailed breakdown of all changes
- Before/After comparison
- Testing checklist
- Known issues and limitations
- Benefits summary fbb14c -
Fix: Use getToolbarConfig() in template instead of getCurrentUser()
- Updated index.phtml to use full config from getToolbarConfig()
- Removed manual config building in template
- Now all config comes from ViewModel (cleaner approach)
- Fixes: Call to undefined method getCurrentUser() error 51af89 -
Refactor AdminToolbar to extend Toolbar with real data
Changes:
- AdminToolbar now extends Toolbar (reuses StoreDataProvider, PageUrlProvider)
- Replaced mock publications with real data from PublicationRepository
- Removed duplicate methods: getStoreId(), getThemeId()
- Removed fake methods: getStoreHierarchy(), getPageTypes()
- Updated getToolbarConfig() to use inherited methods
- Added getCurrentPageId() with URL-based fallback detection
Benefits:
- 130+ lines of code removed (-34%)
- No code duplication
- Single source of truth for store/page data
- Real publications from database
- Automatic bugfixes from parent class
Testing required:
- Open https://magento248.local/admin/breeze_editor/editor/index
- Check publication selector shows real data (or empty if no publications)
- Check scope selector shows real store hierarchy
- Check page selector shows real page types
- Verify all dropdowns work b639cc -
Phase 1B: Complete toolbar implementation with mock data
- Created 3 new widgets: publication-selector, scope-selector, page-selector
- Created HTML templates for all widgets
- Updated toolbar.js coordinator with widget initialization
- Added AdminToolbar ViewModel with mock data
- Updated toolbar.html template with widget containers
Next: Refactor AdminToolbar to use real data via inheritance 71d57b -
feat: Implement Phase 1 - Admin interface foundation
- Add admin controllers (AbstractEditor, Index, Iframe)
- Add admin routes and menu configuration
- Add layout files for editor pages
- Add minimal toolbar with device switcher
- Add component-based template structure (x-magento-init)
- Add CSS styling for admin interface
Phase 1 deliverables:
✅ Admin menu item: Content > Theme Editor
✅ Admin controller structure with ACL
✅ Iframe renders frontend preview
✅ Minimal toolbar (device switcher, status, exit button)
✅ Component-based architecture ready for Phase 2-3
URL: /admin/breeze_editor/editor/index b61f3a -
docs: Update admin migration architecture to component-based approach
- Move all toolbar components to admin context (no PostMessage bridge)
- Shared components in view/base/web/js/toolbar/
- Admin-specific components in view/adminhtml/web/js/editor/toolbar/
- Component-based template structure (x-magento-init pattern)
- Jstest framework moved to base
- Device switcher controls iframe width (CSS only)
- Frontend becomes preview-only (no toolbar) 2cf008 -
docs: Add admin migration planning documentation
- Complete migration plan for moving from token auth to admin interface
- 5 phases with detailed implementation steps and code examples
- ACL permissions structure and GraphQL authentication patterns
- User migration guide and breaking changes documentation
- Testing strategy with 300+ test cases
- Reference materials with Magento code examples
Total: 9 documents, 3,516 lines, ~90KB of planning documentation
Addresses security concerns by replacing token-based authentication
with native Magento admin session + ACL permissions.
Related: Phase 1-5 implementation plans ready to execute c885e6 -
docs: update README with accurate project info and new features
- Add version badges (0.1.0, OSL-3.0 license, PHP 7.4-8.2)
- Update PHP requirements from 8.1+ to 7.4|8.0|8.1|8.2
- Add Dual Color Format Support feature (RGB for Breeze 2.0, HEX for Breeze 3.0)
- Add new Color Format Support section with RGB/HEX examples and auto-detection
- Add GraphQL API Example section with query sample and API features
- Expand Field Types table from 8 to 16 types with RGB/HEX support info
- Update Running Tests section with Backend (25+ PHPUnit) and Frontend (24 JS) tests
- Add links to README-TESTS.md for complete testing documentation
- Update Requirements section with Breeze version info (v2.0+ for RGB, v3.0+ for HEX)
These changes align README with actual codebase state including recent RGB
format support implementation and comprehensive test coverage. 03c098
-
* Repositories are sorted to display abandoned activity on the top of the list