Untagged Commits

This page helps make in-time deliveries for all new goodies.

breezefront / theme-frontend-breeze-blank

2 commits 2
  • head
    • Ability to change dark color using CSS variable 6b71ee

    • Add test presets for theme editor

      - Add 3 presets: Dark Mode, High Contrast, Minimal Clean
      - Demonstrate dot-notation format for preset settings 790cc6

breezefront / module-breeze-algoliasearch

1 commit 1

breezefront / module-breeze-magento-payment-services

1 commit 1

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 (&lt;svg&gt; 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