Recent Activities

This page shows what are we working on.

swissup / module-pro-labels

1 day ago success
  • head
    • Update frontend models for label text fields in system config b743ec

    • Enhance label variables editor with improved variable handling and styling d8e08e

    • Implement LabelText editor with variable support and update configuration ca300f

    • Add new variables for price and discount amount without tax:
      - price_base
      - final_price_base
      - special_price_base
      - discount_amount_base e19b20

  • 1.7.30
    • Version 1.7.30 8e36a3

    • Allow to render prolabels with empty text. Label can have only image uploaded. e300af

swissup / theme-frontend-swissuplabs

2 days ago success

swissup / module-swissuplabs

2 days ago error

breezefront / theme-frontend-breeze-enterprise-apollo

2 days ago success

breezefront / theme-frontend-breeze-blank

2 days ago success

breezefront / module-breeze-theme-editor

2 days ago success
  • 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

  • 0.1.0
    • test: increase reset test timeouts and add CSS preview verification

      Timeout Updates:
      - Increase badge polling timeout from 2s to 3s (20→30 attempts)
      - Increase clickReset wait from 100ms to 500ms
      - Increase listener notification wait from 200ms to 500ms

      New Test:
      - Add test 'CSS preview should remove variable when reset to palette reference'
      - Verifies that CSS variable is removed from live preview after reset
      - Ensures cascade works correctly via var(--palette-color-rgb)
      - Test count increases from 146 to 147

      This test catches regressions in field-reset CSS preview update logic. 238b4f

    • test: add format parameter to CssPreviewManager.setVariable() calls

      After adding RGB format support, setVariable() requires fieldData
      parameter with format field. Update 4 test calls to pass { format: 'rgb' }
      to ensure RGB values are preserved instead of being converted to HEX.

      Fixes test failures where rgb(180, 24, 24) was converted to #b41818. 176d08

    • test: increase default test timeout from 5000ms to 6000ms

      Palette RGB cascade processing adds slight overhead to tests.
      Increasing timeout prevents false failures in publication mode tests. 6c3769

    • fix: update field-reset listener to handle palette references correctly

      - Check if reset value is palette reference (starts with --color-)
      - Remove CSS variable from live preview when reset to palette ref
      - Update CSS variable when reset to direct HEX/RGB value
      - Pass fieldData (format, defaultValue) to CssPreviewManager.setVariable()

      This fixes the issue where resetting RGB field to palette reference
      didn't update the live preview CSS, causing color to remain unchanged.

      Before: Reset → value='--color-brand-amber-dark' → CSS unchanged → wrong color
      After: Reset → removeVariable('--base-color') → cascade works → correct color 7ac678

    • feat: add RGB format support and palette RGB cascade to CssPreviewManager

      Format Support:
      - Add fieldData parameter to setVariable() method
      - Update _formatValue() to accept and pass fieldData
      - Implement _formatColorValue() with RGB/HEX auto-detection
      - Support format field from GraphQL (format: 'rgb' or 'hex')
      - Convert colors based on field format requirements

      Palette RGB Cascade Fix:
      - Generate BOTH HEX and RGB versions of palette colors
      - Set --palette-color (HEX) and --palette-color-rgb (RGB) variables
      - Add removeVariable() method for field reset scenarios
      - Ensures fields with format='rgb' can reference var(--palette-color-rgb)

      This fixes the issue where changing palette color didn't update
      RGB format fields on the page. f50574

    • feat: implement RGB format support in color field handler

      - Read data-format attribute from color input elements
      - Convert colors between HEX and RGB based on field format
      - Update field change handler to pass format to CssPreviewManager
      - Preserve format during palette color selection
      - Handle format in field reset logic

      Color fields with format='rgb' now output RGB format (255, 0, 0)
      instead of HEX format (#ff0000). 3287f9

    • feat: add format field to GraphQL queries and color field template

      - Add 'format' field to getConfig and getConfigFromPublication queries
      - Update color.html template to include data-format attribute
      - Update color field renderer to pass format to template data

      Frontend can now read format attribute from color field DOM elements. 634e86

    • feat: expose color format field in GraphQL schema

      - Add 'format' field to Field type in GraphQL schema
      - Update AbstractConfigResolver to return format for color fields
      - Inject ColorFormatResolver into Config and ConfigFromPublication resolvers
      - Update ConfigTest to verify format field is returned for color fields

      This allows frontend to know whether field expects RGB or HEX format. 24f043

    • feat: add ColorFormatResolver utility for RGB format detection

      - Add ColorFormatResolver utility class for auto-detecting RGB/HEX format
      - Update CssGenerator to use ColorFormatResolver for color fields
      - Append -rgb suffix to palette references when format is RGB
      - Add comprehensive unit tests for ColorFormatResolver (11 test cases)
      - Update CssGeneratorTest to verify RGB format handling

      Backend changes support fields with format='rgb' to reference
      var(--palette-color-rgb) instead of var(--palette-color). aa5336

    • fix: correct GraphQL variable type for getCss query to support PUBLICATION status

      - Change $status type from BreezeThemeEditorStatusCode to BreezeThemeEditorCssStatusCode
      - Allows PUBLICATION status to be used in getCss GraphQL query
      - Fixes "Variable $status of type BreezeThemeEditorStatusCode used in position expecting BreezeThemeEditorCssStatusCode" error
      - Publication Selector now loads historical CSS correctly without GraphQL errors
      - Tests: PHP 225/225, JS 146/146 807dab

    • fix: render CSS style elements in test mode and for admin users

      - Add isTestMode() method to ThemeCssVariables ViewModel to detect ?jstest=true
      - Update inline-css-variables.phtml to render styles in test mode
      - Ensures CSS Manager can initialize during automated tests
      - Fixes 15 failing JS tests related to CSS element detection (#bte-theme-css-variables, #bte-theme-css-variables-draft)
      - All tests now passing: PHP 225/225, JS 146/146 141799

    • fix: always render CSS style elements when admin has access token

      Issue: CSS Manager fails on Breeze Blank (themes without saved values)
      - Error: #bte-theme-css-variables not found after retries
      - Root cause: shouldRender() blocks ALL rendering if CSS is empty
      - Affects: New/blank themes without customizations

      Solution (3 fixes):

      1. inline-css-variables.phtml - Smart render logic:
      - Admin (has token) → ALWAYS render both styles (even empty)
      - Regular user → only published if has content
      - Ensures CSS Manager always finds required elements
      - Performance optimized (no empty styles for regular users)

      2. ThemeCssVariables.php - Add hasAccessToken() public method:
      - Allows template to detect admin users
      - Uses existing token validation logic
      - Enables conditional rendering strategy

      3. panel.js - Fix PUBLICATION mode loading (related fix):
      - Handle publicationStatusChanged event correctly
      - Use _loadConfigFromPublication() for PUBLICATION status
      - Use _loadConfig() for DRAFT/PUBLISHED status
      - Prevents GraphQL type mismatch errors

      4. publication-mode-test.js - Add mock for test (related fix):
      - Register mock for publicationId=1
      - Ensures test doesn't timeout waiting for real GraphQL
      - All 146 JS tests now pass

      Result:
      ✅ Breeze Blank now editable for admins (main fix)
      ✅ CSS Manager always finds required elements
      ✅ Panel loads correctly for PUBLICATION mode
      ✅ All 146 JS tests pass (was 145/146)
      ✅ All 225 PHP tests pass
      ✅ Performance optimized for regular users
      ✅ Backward compatibility preserved

      Related commits:
      - c6af898 (Jan 27) - First attempt, partially fixed draft rendering
      - 7594706 (recent) - Split GraphQL enum types for PUBLICATION 1cce79

    • Add comprehensive Phase 2 tests for 6 core components

      Implements comprehensive unit test coverage for critical services and providers:

      - PresetService (12 tests, 43 assertions): Preset discovery, parsing, and application
      - SaveValue mutation (8 tests, 30 assertions): GraphQL value saving with validation
      - CompareProvider (11 tests, 57 assertions): Draft vs Published comparison logic
      - StatusProvider (12 tests, 29 assertions): Status mapping with cache layer
      - ValueService (15 tests, 40 assertions): CRUD operations with SearchCriteria
      - PaletteResolver (13 tests, 25 assertions): CSS variable resolution and color mapping

      Total: 71 new tests, 224 assertions

      Phase 2 completes the high-priority component testing, bringing total coverage to
      225 tests with 728 assertions. All tests passing. 16a138

    • Add comprehensive tests for SavePaletteValue mutation resolver

      Test Coverage:
      - SavePaletteValue mutation: 14 tests covering palette color saving
      * Color format validation: HEX with/without hash, shorthand HEX, RGB legacy
      * CSS variable validation: Must start with '--color-'
      * Color normalization: Auto-convert RGB to HEX, expand shorthand
      * Theme detection: Auto-detect by storeId, explicit themeId override
      * Database operations: Save success, exception handling
      * Affected fields calculation: Zero to multiple affected fields
      * Constants: Always saves to '_palette' section with statusId=1

      Implementation Notes:
      - Palette changes always saved as PUBLISHED (statusId=1)
      - RGB format auto-converted to HEX for backward compatibility
      - Uses saveMultiple() with insertOnDuplicate for upsert behavior
      - Returns count of fields affected by palette color change

      Test Results:
      - Total: 154 tests, 504 assertions, all passing
      - Phase 1 complete: All planned service + mutation tests implemented 834ce1

    • Add comprehensive tests for Publish and Rollback mutation resolvers

      Test Coverage:
      - Publish mutation: 10 tests covering publish workflow via GraphQL
      * Input validation: Title requirement, optional description
      * Theme detection: Auto-detect by storeId, explicit themeId override
      * Response formatting: Success message, publication data, user metadata
      * Changes formatting: Multi-change scenarios (modified/added/deleted)
      * Edge cases: Empty changes, missing user metadata

      - Rollback mutation: 8 tests covering rollback workflow via GraphQL
      * Publication validation: Target existence check, not found exception
      * Response formatting: Publication data with rollback flags
      * User metadata: Always null (unlike Publish)
      * Changes: Always null in response (only changes count returned)
      * Edge cases: Zero changes count, optional description

      Test Results:
      - Total: 140 tests, 464 assertions, all passing
      - Phase 1 progress: All critical service + mutation tests completed fc0b2d

    • Add comprehensive tests for ValueInheritanceResolver and ConfigProvider

      Test Coverage:
      - ValueInheritanceResolver: 20 tests covering theme inheritance logic
      * resolveAllValues: Single theme, multi-level hierarchy, deep merging
      * resolveSingleValue: Child/parent/grandparent resolution, fallback to config
      * isValueInherited: Inheritance detection logic
      * getInheritedFromTheme: Source theme tracking
      * Edge cases: Empty hierarchies, deep nesting (4 levels), userId param

      - ConfigProvider: 21 tests (19 passing, 2 skipped for file I/O)
      * deepMerge: Simple values, nested arrays, special sections handling
      * mergeSections: Update by ID, add new, preserve fields, settings merge
      * mergeSettings: Update by ID, add new, full array merge
      * Cache management: Clear specific/all themes
      * Edge cases: Empty arrays, complex multi-level inheritance
      * Skipped: File I/O tests (better suited for integration tests)

      Test Results:
      - Total: 122 tests, 356 assertions, all passing
      - Phase 1.1 progress: 4 of 6 service/provider test suites completed 7bddb3

    • Add comprehensive tests for ImportExportService and PublishService

      Test Coverage:
      - ImportExportService: 17 tests covering export/import workflows
      * Export: PUBLISHED/DRAFT status, empty values, filename format, dot notation
      * Import: JSON validation, overwrite logic, value serialization, error handling
      - PublishService: 10 tests covering publish/rollback workflows
      * Publish: Changes detection, publication creation, changelog, value migration
      * Rollback: Publication restoration, value copying, changelog preservation

      Bug Fixes:
      - Fixed inverted overwrite logic in ImportExportService.php:133
      * Before: if (!overwriteExisting) would delete existing values
      * After: if (overwriteExisting) correctly deletes when overwrite=true
      - Fixed PHPUnit 10 compatibility (replaced deprecated withConsecutive)

      Test Results:
      - Total: 81 tests, 238 assertions, all passing
      - Phase 1.1 progress: 2 of 6 service test suites completed da6207

    • Refactor: Split GraphQL enum to prevent PUBLICATION in wrong queries

      Problem:
      - BreezeThemeEditorStatusCode enum included PUBLICATION value
      - GraphQL allowed PUBLICATION for all queries (Config, Values, ExportSettings)
      - Runtime validation in resolvers was needed to reject invalid status

      Solution:
      - Create BreezeThemeEditorCssStatusCode enum (DRAFT, PUBLISHED, PUBLICATION)
      - Update BreezeThemeEditorStatusCode enum (DRAFT, PUBLISHED only)
      - Use BreezeThemeEditorCssStatusCode for getThemeEditorCss query
      - Use BreezeThemeEditorCssStatusCode for BreezeThemeEditorCssOutput.status

      Benefits:
      - Type-safe: GraphQL schema prevents PUBLICATION in Config/Values/Export
      - Better developer experience: IDE autocomplete shows only valid values
      - Runtime validation still in place as defense-in-depth
      - Clear documentation via @doc annotation

      Tests:
      - All 54 tests passing
      - GraphQL schema validated successfully

      Related commits:
      - 5e16865 (Runtime validation in resolvers) 759470

    • Add PUBLICATION validation to Config, Values, and ExportSettings resolvers

      Problem:
      - GraphQL enum allows PUBLICATION status for all resolvers
      - Only GetCss.php properly handles PUBLICATION
      - Config/Values/ExportSettings called StatusProvider with PUBLICATION
      causing 'Status not found' error

      Solution:
      - Add validation in Config.php, Values.php, ExportSettings.php
      - Throw GraphQlInputException with clear error messages
      - Direct users to correct query (ConfigFromPublication)
      - Fix bug in ExportSettings: pass statusCode not statusId to export()

      Tests:
      - Add ConfigTest.php (7 tests)
      - Add ValuesTest.php (7 tests)
      - Add ExportSettingsTest.php (5 tests)
      - Total: 54 tests, 160 assertions, all passing

      Related commits:
      - 98bda63 (GetCss.php fix)
      - 1fa1d1b (GetCss tests) 5e1686

    • Add comprehensive unit tests for GetCss resolver

      - Test PUBLICATION status validation (bug fix verification)
      - Test all status types (PUBLISHED, DRAFT, PUBLICATION)
      - Test auto-detection and default behaviors
      - Test hasContent validation for CSS output
      - Test error handling and edge cases

      Coverage:
      - 10 test cases covering resolve(), hasRealCssContent(),
      and generateCssFromPublication() methods
      - All tests pass (35/35 total)
      - Uses ChangelogSearchResultsInterface for proper type safety 1fa1d1

    • Fix: Validate publicationId when status is PUBLICATION

      Previously, when status='PUBLICATION' was passed without publicationId,
      the code would fall through to CssGenerator::generate() which would
      attempt to find 'PUBLICATION' status in the database via StatusProvider.
      This status doesn't exist in DB (only 'draft' and 'published'), causing
      'Status "PUBLICATION" not found' error.

      Changes:
      - Add explicit validation: throw GraphQlInputException if status is
      PUBLICATION but publicationId is missing
      - Add GraphQlInputException import
      - Improve error message clarity for API consumers

      Fixes issue where saving button background after compilation fails
      with 'Status "PUBLICATION" not found' error. 98bda6

    • Refactor: Extract duplicate palette processing logic into reusable method

      - Extract 45 duplicate lines into processPaletteColor() method
      - Reduce code duplication in generate() and generateFromValuesMap()
      - Fix test mocks to return correct data structure format
      - Update test defaults to properly test value changes

      Resolves phpcpd findings: 0.49% duplication → 0% 68399a

    • Fix calc() usage in LESS files

      Replace unescaped calc() with ~"calc(...)" to ensure proper LESS compilation.

      Changes:
      - panels/_panels.less: Use interpolation for @bte-toolbar-height variable
      - panels/_theme-editor-panel.less: Escape calc() for width calculation
      - components/_publication-selector.less: Escape calc() for top positioning
      - _mixins.less: Escape 3 calc() instances (top, max-height)

      All calc() expressions now use escaped strings to prevent LESS from
      attempting to evaluate them during compilation. The original file at
      _utilities.less:50 already used the correct ~"calc(...)" syntax.

      Total: 6 calc() expressions fixed across 4 files c2c796

    • Add comprehensive test coverage for color format features

      This commit adds 41 new tests covering RGB wrapper support and dual-format
      palette color generation functionality.

      Backend PHP Tests (25 tests):
      - ColorConverterTest.php (15 tests)
      * RGB wrapper detection (rgb() and rgba())
      * RGB to HEX conversion with wrapper support
      * HEX to RGB conversion with wrapper handling
      * Alpha channel stripping in rgba() format
      * Backward compatibility verification
      * Round-trip conversion tests

      - CssGeneratorTest.php (10 tests)
      * Dual format palette generation (HEX + RGB variants)
      * Smart mapping for format property (hex/rgb)
      * CSS variable structure validation
      * Multiple format requirements handling
      * Palette color ordering verification

      Frontend JavaScript Tests (16 tests):
      - color-utils-rgb-wrapper-test.js (6 tests)
      * isRgbColor() wrapper format detection
      * rgb() and rgba() format recognition
      * Integration with conversion functions
      * Edge case handling

      - palette-format-mapping-test.js (8 tests)
      * Dual format generation simulation
      * Smart mapping verification (format: hex vs rgb)
      * RGBA usage pattern validation
      * CSS structure verification
      * Backward compatibility for direct colors

      - palette-integration-test.js (+2 new tests)
      * Format property support in field configs
      * RGBA usage with RGB format variables

      Configuration:
      - Added test registration in Block/TestRunner.php
      - Created tests/bootstrap.php for PHPUnit
      - Maintained existing phpunit.xml.dist configuration

      Documentation:
      - Created comprehensive README-TESTS.md with:
      * Running tests (frontend and backend)
      * Writing new tests (examples and guidelines)
      * Test structure documentation
      * Best practices and troubleshooting

      Test Execution:
      - Frontend: http://localhost/pub/?jstest=1&autorun=1
      - Backend: bin/clinotty bash -c "cd vendor/swissup/module-breeze-theme-editor && ../../bin/phpunit"

      Total: 1,620 lines of test code
      Coverage: RGB wrapper support + dual-format palette generation 1f724e

    • Generate both HEX and RGB formats for palette colors

      Problem:
      - Palette colors were generated only in HEX format
      - Fields with format: "rgb" using palette references failed
      - Example: rgba(var(--base-color), 1) → rgba(#c87604, 1) → black

      Solution:
      - Generate BOTH formats for each palette color:
      • --color-brand-primary (HEX)
      • --color-brand-primary-rgb (RGB)
      - Smart mapping in formatColor(): append -rgb suffix when format=rgb
      - Backward compatible: existing configs work without changes

      Changes:
      - CssGenerator::generate() - Generate both palette formats (lines 102-112)
      - CssGenerator::generateFromValuesMap() - Generate both formats (lines 263-274)
      - CssGenerator::formatColor() - Append -rgb suffix for RGB fields (lines 433-439)
      - Updated PHPDoc for formatColor() to document new behavior

      Example Output:
      Palette: --color-brand-amber-dark: #c87604;
      --color-brand-amber-dark-rgb: 200, 118, 4;

      Field with format: "rgb" → var(--color-brand-amber-dark-rgb)
      Field with format: "hex" → var(--color-brand-amber-dark)

      Fixes: rgba(var(--base-color), 1) now correctly displays color instead of black fdd4c1

    • Support rgb() wrapper in color default values

      - Update ColorConverter::isRgb() to recognize rgb(r, g, b) format
      - Update ColorConverter::rgbToHex() to strip rgb() wrapper before conversion
      - Update ColorConverter::hexToRgb() to handle rgb() input and strip alpha
      - Update ColorUtils.isRgbColor() (JS) to recognize rgb(r, g, b) format
      - Support both bare "17, 24, 39" and wrapped "rgb(17, 24, 39)" formats
      - RGBA alpha channel is ignored (CSS variables don't support transparency)
      - Backward compatible with existing configurations

      Fixes auto-detection of color format when default uses rgb() wrapper.
      Users can now use "default": "rgb(17, 24, 39)" in field configuration. 326ef5

    • Add configurable color format support (hex/rgb/auto)

      - Add 'format' property to COLOR field configuration
      - Support 'hex' (Breeze 3.0), 'rgb' (Breeze 2.0), and 'auto' formats
      - Default logic: auto if default exists, otherwise hex
      - Update formatColor() to accept format and defaultValue parameters
      - Refactor formatValue() to accept full field config
      - Add 'format' field to GraphQL BreezeThemeEditorField type
      - Backward compatible with existing themes 8af898

    • Change CssGenerator to output HEX format for Breeze 3.0 themes

      - Update formatColor() to return HEX instead of RGB
      - Breeze 3.0 uses 'color: var(--base-color)' instead of 'color: rgb(var(--base-color))'
      - Add automatic RGB->HEX migration for legacy database values
      - Fix palette and preset fields not disabling in PUBLISHED mode
      - Add publicationStatusChanged event triggers in css-manager
      - Add palette-preset-disabled-test for edit restrictions 529786

    • Fixed error when wikimedia/less.php < 5 c3c597

    • Update SavePaletteValue mutation to accept HEX format

      - Accept HEX format in GraphQL mutation input
      - Convert HEX to RGB before saving to database
      - Update GraphQL schema to reflect HEX input
      - Maintain RGB storage for backward compatibility 98cfc6

    • Refactor: CssGenerator converts HEX to RGB for CSS output

      - Accept HEX input values (aligned with Breeze 3.0)
      - Convert HEX to RGB for CSS variable output
      - Use ColorConverter for conversions
      - Remove duplicate hexToRgb() method e4a13f

    • Refactor: PaletteResolver handles HEX format comparison

      - Compare colors using HEX format instead of RGB
      - Convert legacy RGB values for backward compatibility
      - Use ColorConverter for format conversions
      - Simplify color matching logic 4a8fcf

    • Refactor: PaletteProvider returns HEX format values

      - Return HEX format instead of RGB for palette colors
      - Convert legacy RGB saved values to HEX on load
      - Remove hexToRgb() method (replaced by ColorConverter)
      - Use ColorConverter utility for conversions 635d25

    • Update frontend modules to use HEX format from PaletteManager

      - Update css-preview-manager.js subscriber callback
      - Update color field renderer for HEX values
      - Update palette-section-renderer.js listener
      - Remove RGB parameter handling (now HEX only) e2670f

    • Refactor: Migrate PaletteManager to HEX color format

      - Store colors in HEX format instead of RGB (Breeze 3.0)
      - Auto-convert legacy RGB values to HEX on init
      - Update notify() to use HEX only (removed rgbValue param)
      - Simplify dirty state tracking with single value format
      - Maintain backward compatibility with RGB inputs 018076

    • Add HEX color utilities to ColorUtils

      - Add normalizeHex() for consistent HEX format
      - Add isHexColor() and isRgbColor() validators
      - Support HEX/RGB format detection and conversion
      - Enables Breeze 3.0 migration to HEX format 205e20

    • Add ColorConverter utility class for PHP

      - Centralized color format conversion (HEX ↔ RGB)
      - Used by backend services for Breeze 3.0 HEX migration
      - Provides rgbToHex() and hexToRgb() methods df7e1a

    • Test: Fix color-field-palette-ref-test.js to expect HEX format

      - Update color value assertion from '25, 121, 195' to '#1979c3'
      - Ensures palette reference lookup tests use correct format 2cc55f

    • Test: Fix palette-integration-test.js to expect HEX format

      - Update color value assertion from '25, 121, 195' to '#1979c3'
      - Validates PaletteManager config loading with correct format 19b7ea

    • Test: Fix palette-section-renderer-test.js to expect HEX format

      - Update color value assertion from '255, 0, 0' to '#ff0000'
      - Ensures test matches actual PaletteManager behavior 899032

    • Test: Fix palette-manager-test.js to expect HEX format instead of RGB

      - Update color value assertion from '25, 121, 195' to '#1979c3'
      - Update color value assertion from '255, 0, 0' to '#ff0000'
      - Aligns tests with Breeze 3.0 HEX format migration 13d07c

    • Fix: PaletteProvider now reads saved values from database

      Problem:
      - PaletteProvider always returned default values from theme.json
      - Never read saved palette values from database (section: _palette)
      - Result: Modified badge count was wrong (compared defaults to defaults)

      Solution:
      - Read saved values from $valuesMap['_palette.--color-brand-primary']
      - Use saved RGB value if exists, otherwise fallback to default
      - Now color.value correctly reflects saved state from database

      Data Flow:
      1. SavePaletteValue.php saves to: section='_palette', setting=cssVar, value=RGB
      2. Config.php reads all values and builds $valuesMap['section.setting']
      3. PaletteProvider.php now checks $valuesMap['_palette.' . cssVar]
      4. Frontend receives correct saved values for comparison

      Example:
      - Before: value='25,121,195' (default), default='#1979c3'
      - After: value='215,127,4' (saved), default='#1979c3'
      - Modified state correctly detected ✓ 88391c

    • Fix: Convert HEX to RGB when comparing palette modified state

      Problem:
      - PaletteProvider sends color.value as RGB ("25, 121, 195")
      - PaletteProvider sends color.default as HEX ("#1979c3")
      - isColorModified() was comparing RGB to HEX directly
      - Result: All 20 colors showed as "modified" even when unchanged

      Solution:
      - Convert color.default from HEX to RGB before comparison
      - Both values now normalized to RGB format: "25,121,195"
      - Correct modified count: only colors that truly differ from defaults

      Example:
      - value: "25, 121, 195" → normalized: "25,121,195"
      - default: "#1979c3" → hexToRgb: "25, 121, 195" → normalized: "25,121,195"
      - Match! Not modified ✓ d3d373

    • Feature: Add Modified badge for palette colors in header

      Implement visual indicators for palette color modifications:

      Badge System (Header Only):
      - Changed (N) badge: shows count of unsaved palette colors
      - Reset (↺) button: appears with Changed badge to discard changes
      - Modified (N) badge: shows count of colors different from defaults
      - Both Changed and Modified badges can appear simultaneously

      Swatch Border Indicators:
      - Orange border (#f97316): modified colors (saved but != default)
      - Yellow border (#fbbf24): dirty colors (unsaved changes)
      - Dirty border takes precedence when both states apply

      Implementation:
      - badge-renderer.js: Add 4 palette badge rendering methods
      - palette-manager.js: Add isColorModified(), getModifiedCount(), getDirtyCount()
      - palette-section-renderer.js: Add _updateHeaderBadges() and _updateSwatchModifiedState()
      - palette-section.html: Replace static reset button with dynamic badge container
      - _palette-section.less: Add modified/dirty border styles, update reset button styling

      Technical Details:
      - Badges update on color change, save, and reset operations
      - Modified state compares saved RGB values with theme defaults using ColorUtils.normalizeRgb()
      - Swatch tooltips include "Modified from default" indicator
      - Console logging for badge state changes 134aba

    • Fix: Palette cascade now properly updates dependent fields via CSS var()

      Problem:
      - When palette color changed, dependent fields didn't update visually
      - ColorUtils.hexToRgb() received RGB format and showed warnings
      - Live preview overrode field CSS variables with direct values
      - CSS cascade via var(--palette-color) didn't work

      Solution:
      - ColorUtils.hexToRgb(): Added RGB format passthrough (defensive)
      - If input is already RGB (e.g., '255, 0, 0'), return as-is
      - Prevents warnings when accidentally passed RGB instead of HEX

      - css-preview-manager.js: Use hexValue instead of rgbValue
      - Palette cascade now passes HEX for proper conversion
      - Correct architecture: color type receives HEX input

      - css-preview-manager.js: Remove dependent fields from live preview
      - When palette color changes, delete dependent field CSS vars from changes{}
      - Allows CSS cascade to work: --base-color: var(--palette-color)
      - Live preview only contains palette color, not dependent overrides

      Result:
      - ✅ Palette color changes cascade to dependent fields visually
      - ✅ No console warnings about invalid HEX format
      - ✅ CSS var() references work correctly
      - ✅ Direct field edits still work (adds to live preview)
      - ✅ All existing tests passing 79e130

    • Refactor: Extract badge rendering to BadgeRenderer module

      - Created BadgeRenderer module with separate HTML templates
      - badge-renderer.js: Centralized badge rendering logic
      - Templates use RequireJS text! plugin for loading
      - Lazy compilation with mageTemplate caching for performance

      - Extracted inline badge templates to separate files:
      - badges/dirty.html: 'Changed' badge with pulsing icon
      - badges/modified.html: 'Modified' badge for saved customizations
      - badges/reset-button.html: Reset button with data attributes

      - Updated field-renderers/base.js to delegate to BadgeRenderer
      - Removed ~30 lines of inline HTML template code
      - Added BadgeRenderer import
      - renderBadges() now delegates to BadgeRenderer.renderFieldBadges()
      - Maintains backward compatibility

      - Added 8 unit tests for BadgeRenderer
      - Test individual badge rendering (dirty, modified, reset)
      - Test combined badge rendering (all state combinations)
      - Test template caching behavior
      - Registered in TestRunner.php

      - Benefits:
      - Modular: Badge logic isolated (like ColorUtils)
      - Maintainable: HTML templates in separate files
      - DRY: Single source of truth for badge HTML
      - Extensible: Ready for palette badge implementation
      - All 123 tests passing 54b322

    • Fix: Handle rgb() format and negative values in ColorUtils

      - ColorUtils.hexToRgb() now accepts rgb() function format (e.g., rgb(255, 0, 0))
      - Extracts RGB values from rgb() before hex validation
      - Returns formatted string '255, 0, 0'
      - ColorUtils.rgbToHex() correctly handles negative RGB values
      - Fixed regex from /\d+/g to /-?\d+/g to capture minus sign
      - Enables proper clamping of negative values to 0
      - Updated publication-mode-test.js to check for correct RGB format
      - Changed from 'rgb(180, 24, 24)' to '180, 24, 24'
      - CSS variables store RGB without rgb() wrapper
      - All 123 tests passing 3d901d

    • Fix: Use correct TestFramework.suite() instead of TestSuite.create()

      - Change import from 'test-suite' to 'test-framework'
      - Use TestFramework.suite() method (consistent with other tests)
      - Fixes: Unable to resolve test-suite.js error d7dd4a

    • Refactor: Extract color utilities to ColorUtils module

      - Create centralized ColorUtils module (color-utils.js)
      - hexToRgb() - Convert HEX to RGB with shorthand support (#fff)
      - rgbToHex() - Convert RGB to HEX with value clamping
      - normalizeRgb() - Remove whitespace for comparison
      - compareRgb() - Compare RGB strings ignoring formatting

      - Refactor PaletteManager
      - Replace duplicate hexToRgb/rgbToHex implementations
      - Add @deprecated wrapper methods for backwards compatibility
      - Delegate to ColorUtils for all color conversions

      - Refactor CssPreviewManager
      - Remove duplicate _hexToRgb/rgbToHex methods
      - Use ColorUtils for all color operations

      - Add comprehensive test suite (color-utils-test.js)
      - 8 new tests covering all ColorUtils methods
      - Test edge cases: shorthand HEX, whitespace, clamping, round-trips

      - Update PaletteManager tests
      - Remove unit tests (moved to color-utils-test.js)
      - Keep integration tests for wrapper methods

      Total tests: 125 (was 117, +8 new)

      Benefits:
      - Single source of truth for color conversions
      - Eliminates duplicate code (3 implementations → 1)
      - Consistent behavior across frontend
      - Better tested with dedicated test suite
      - Easier to maintain and extend 55ad12

    • fix: prevent isDirty flag on field reset by removing change event trigger

      - Fix field type detection to look on input element first (data-type attribute)
      - Remove .trigger('change') from BaseHandler.updateFieldUIAfterReset()
      - Prevents isDirty being set to true again after reset clears it
      - CSS preview is handled by 'field-reset' event listener in panel.js
      - Fixes test: 'badge should disappear after reset button is clicked' b80272

    • fix: remove pickr.setColor() from cancel handler to prevent error

      Issue: Click on Cancel button → Pickr error in console:
      Uncaught TypeError: Cannot read properties of null (reading 'interaction')
      at E._updateOutput (pickr.min.js:2:19704)

      Root cause:
      pickr.on('cancel') handler (line 360) called:
      pickr.setColor(currentColor); // Triggers async events
      self._closeAllPopups(); // Destroys pickr instance

      Sequence:
      1. pickr.setColor() called → triggers 'change' event (async)
      2. _closeAllPopups() → pickr.destroyAndRemove() → _root = null
      3. Async 'change' event fires → tries to access _root.interaction → null ref error

      Solution:
      Remove pickr.setColor() call from cancel handler.

      Reasoning:
      - Cancel means 'discard changes and close'
      - Popup is being destroyed anyway (_closeAllPopups)
      - No need to update pickr widget state before destroying it
      - Just restore input/trigger values and close

      Result:
      ✅ Cancel button works without errors
      ✅ Original color restored to input/trigger
      ✅ Popup closes cleanly 60629e

    • fix: preserve palette reference on trigger after field reset

      Issue: After reset, color field loses palette binding and doesn't update when palette changes

      Scenario:
      1. Field text_color = '--color-brand-amber-dark' (palette ref)
      2. Open color popup → change color → close
      3. Click Reset button → value restored to '--color-brand-amber-dark'
      4. Go to Palette section → change '--color-brand-amber-dark' color
      5. ❌ Field text_color does NOT update (palette cascade broken)

      Root cause:
      updateFieldUIAfterReset() (line 590) set data-palette-ref on $input:
      $input.attr('data-palette-ref', value);

      BUT did NOT set it on $trigger (.bte-color-trigger).

      PaletteManager cascade looks for palette-ref on BOTH elements:
      $field.filter('[data-palette-ref="' + cssVar + '"]')

      Result: After reset, trigger missing data-palette-ref → cascade skip

      Solution:
      Set/remove data-palette-ref on BOTH $input AND $trigger:

      1. isPaletteRef branch (line 593-595):
      $input.attr('data-palette-ref', value);
      $trigger.attr('data-palette-ref', value); // ← NEW

      2. Regular HEX branch (line 599-601):
      $input.removeAttr('data-palette-ref');
      $trigger.removeAttr('data-palette-ref'); // ← NEW

      Result:
      ✅ Reset → palette ref preserved on both elements
      ✅ Palette change → cascade finds trigger → field updates
      ✅ Consistent with other palette ref operations (lines 382-383, 49-50, 296-297) f08d7b

    • fix: calculate palette usage counts from actual saved values, not config defaults

      Issue: Palette swatches show 'Used in 0 fields' when colors ARE actually used

      Example:
      - Field text_color has value: '--color-brand-amber-dark' (saved in DB)
      - But UI shows: 'Used in 0 fields' ❌
      - Should show: 'Used in 1 fields' ✅

      Root cause:
      PaletteProvider->calculateUsageCounts() only checked field DEFAULTS from config:
      $default = $setting['default'] ?? ''; // e.g., 'rgb(17, 24, 39)'

      This missed palette references in ACTUAL saved values (from DB).

      Solution (Variant A):
      Pass $valuesMap through the entire chain to count real usage:

      1. PaletteProvider.php:
      - getPalettes(): Accept $valuesMap parameter (default empty array)
      - getPaletteById(): Accept $valuesMap parameter
      - processPalette(): Accept and pass $valuesMap to calculateUsageCounts()
      - calculateUsageCounts(): Check ACTUAL values first, fallback to defaults
      * Supports direct refs: '--color-brand-primary'
      * Supports var() format: 'var(--color-brand-primary)'

      2. AbstractConfigResolver.php:
      - formatPalettes(): Accept $valuesMap and pass to PaletteProvider

      3. Config.php:
      - Pass $valuesMap to formatPalettes() (line 97)

      4. ConfigFromPublication.php:
      - Pass $valuesMap to formatPalettes() (line 90)

      Result:
      - Usage counts now reflect REAL field values from DB
      - data-usage attribute shows correct count
      - Tooltip: 'Used in X fields' accurate ✅

      Backward compatible:
      - $valuesMap parameter is optional (default [])
      - Works with both DRAFT and PUBLISHED configs
      - No breaking changes to existing code 432f5a

    • fix: close color popup when clicking inside iframe preview

      Issue: Palette popup doesn't close when clicking on .breeze-device-frame

      Root cause:
      - Document click listener (line 81) only catches clicks on main document
      - Clicks inside iframe don't bubble to parent document
      - Need separate listener on iframe.contentDocument

      Solution:
      1. Added _attachIframeClickListener() helper:
      - Finds #breeze-device-frame iframe
      - Waits for iframe load if not ready
      - Attaches click listener to iframe document
      - Closes popup on any click inside iframe

      2. Call _attachIframeClickListener() in _openPopup():
      - After popup is positioned
      - Ensures iframe listener is active when popup opens

      3. Cleanup in _closeAllPopups():
      - Remove iframe document click listener
      - Remove iframe load event listener
      - Prevents memory leaks

      Result:
      ✅ Click outside popup (main doc) → closes
      ✅ Click inside iframe → closes
      ✅ Click on backdrop → closes
      ✅ ESC key → closes 154623

    • fix: compare palette values against defaults & always render draft style

      Issue: Draft CSS not generating → <style id="bte-theme-css-variables-draft"> missing → CSS Manager error

      Root cause (commit 4ed4faf):
      - Removed default palette injection to reduce CSS bloat
      - But forgot to add comparison logic for user-modified palette values
      - Result: ALL palette values from DB added to CSS (even if = defaults)
      - If all values = defaults → empty CSS → template skips rendering

      Solution (2 fixes):

      1. CssGenerator.php - Add palette default comparison:
      - New method: extractPaletteDefaults() - builds css_var → default_hex map
      - Updated generate() palette loop (lines 59-92)
      - Updated generateFromValuesMap() palette loop (lines 199-226)
      - Logic: Compare DB value (RGB) vs config default (HEX→RGB)
      - Skip if equal (use Breeze base), output if different (user customization)

      2. inline-css-variables.phtml - Always render draft style:
      - Removed if($hasDraftCss) condition
      - Draft <style> now always present (even if empty)
      - Ensures CSS Manager always finds draft element

      Result:
      ✅ Palette values = defaults → not output (minimal CSS)
      ✅ Palette values ≠ defaults → output (user changes preserved)
      ✅ Draft <style> always exists → CSS Manager works
      ✅ Live preview works
      ✅ Consistent logic with regular fields (lines 136-138) c6af89

    • fix: remove palette-ref when user types color manually

      This fixes the badge issue where 'Changed' badges and reset buttons
      only appeared when using the color picker popup, but not when typing
      directly into color input fields.

      Root cause: When user typed a color value, data-palette-ref attribute
      was not cleared, causing BaseHandler.getInputValue() to read the old
      palette reference instead of the new typed value. This resulted in
      PanelState comparing old === old, so isDirty stayed false.

      Solution: Always remove data-palette-ref attributes when user types
      manually, regardless of whether the popup is open or not.

      Tests: All 4 badge-related tests now pass (113/117 → 117/117). 35988b

    • debug: add extensive logging to track badge update flow

      Added logging to:
      1. panel-state.js - commented out console.trace (too verbose)
      2. css-manager.js - commented out console.trace calls
      3. color.js - log when input event fires with validation status
      4. panel.js - log when panel callback is triggered and badges updated

      This will show the complete flow:
      🎨 ColorHandler receives input
      → 🔄 Field changed (BaseHandler)
      → 🔔 Panel callback triggered
      → ✅ Badges updated

      Will help identify where the chain breaks. 0c04c2

    • debug: add detailed logging to changeFieldValue

      Added extensive console logging to debug why badges don't appear:
      - Field info (section, field, selector, values)
      - Input element and field container
      - Before/after trigger event
      - Badge state on each polling attempt (every 100ms)
      - Header HTML when badge fails to appear

      This will help identify why input events aren't triggering badge updates. 695b67

    • test: update mock palette test to expect 3 groups

      The mockPaletteConfig now has 3 groups instead of 2:
      1. brand (3 colors)
      2. semantic (2 colors)
      3. test (1 color - added for Color Renderer tests)

      Updated assertion from 2 to 3 groups. 4bf1fe

    • fix: use jQuery .trigger() instead of native dispatchEvent

      PROBLEM:
      Badge tests were timing out because badges didn't appear after
      changing field value. Console showed:
      'Changed badge should appear after field is modified'
      'Badge did not appear after field change'

      ROOT CAUSE:
      Using native dispatchEvent() to trigger 'input' event doesn't work
      with jQuery delegated event handlers. ColorHandler uses:
      $element.on('input', '.bte-color-input', handler)

      This is event delegation, and native events don't bubble through
      jQuery's delegation system.

      SOLUTION:
      Changed from:
      $input[0].dispatchEvent(new Event('input', { bubbles: true }))

      To:
      $input.trigger('input')

      jQuery's .trigger() properly fires delegated event handlers.

      This fixes all 4 badge reset tests that were timing out. a4edd1

    • fix: add missing isActive, updatePreview, and refresh methods

      PROBLEM:
      panel.js:162 was calling CssPreviewManager.isActive() and
      CssPreviewManager.updatePreview() but these methods didn't exist,
      causing TypeError when field reset button was clicked.

      Also panel.js:858 called CssPreviewManager.refresh() which was missing.

      SOLUTION:
      Added 3 missing public API methods to CssPreviewManager:

      1. isActive() - checks if iframeDocument is initialized
      2. updatePreview() - public wrapper for _updateStyles()
      3. refresh() - alias for updatePreview()

      These methods were referenced in panel.js but never implemented.
      Now field reset correctly updates CSS preview without errors. d4eeed

    • test: add badge polling and better error messages

      PROBLEM:
      Tests 5 and 6 were failing with 'Reset button not found' because
      badges didn't appear after changing field value.

      ROOT CAUSE:
      The 200ms timeout was insufficient for badge rendering, and there
      was no verification that badges actually appeared before trying
      to click the reset button.

      SOLUTION:
      1. Updated changeFieldValue() helper:
      - Added polling mechanism (max 2 seconds, checks every 100ms)
      - Waits for both hasDirtyBadge and hasResetButton before proceeding
      - Returns descriptive error if badge doesn't appear
      - Added optional waitForBadge parameter (default: true)

      2. Updated clickReset() helper:
      - Added detailed error message with badge state
      - Logs $header HTML for debugging
      - Better console logging

      3. Updated Tests 5 and 6:
      - Added error handling for changeFieldValue() callback
      - Proper error propagation to test framework

      This makes tests more reliable and easier to debug. fd99a0

    • test: fix Color Renderer tests to use PaletteManager

      PROBLEM:
      Two tests were failing:
      - 'should resolve palette ref from CSS variables' (expected #ff0000, got #000000)
      - 'should fallback through resolution chain' (expected #abcdef, got #000000)

      ROOT CAUSE:
      Tests created CSS variables via <style> tags, but ColorRenderer doesn't
      read from DOM - it only looks up colors in PaletteManager.palettes.

      SOLUTION:
      1. Added test color to fixtures (--color-test-red: 255, 0, 0)
      2. Updated Test 6 to initialize PaletteManager with test data instead
      of creating <style> tags
      3. Test 7 now works correctly with fallback logic from previous commit

      This matches how the system actually works in production. 7abc67

    • fix: add fallback to field.default in ColorRenderer

      When a palette reference is not found in PaletteManager, the system
      now falls back to field.default instead of always returning #000000.

      Changes:
      - Updated _getPaletteHexFromMapping() signature to accept fallbackDefault param
      - Updated prepareData() to pass field.default as fallback
      - All return points now use fallbackDefault || '#000000'
      - Added logging to show fallback value when palette ref not found

      This fixes color-renderer-test.js Test 7 'should fallback through
      resolution chain' which expects #abcdef but was getting #000000. f72ab2

    • fix: prevent PanelState.init() from clearing event listeners

      ROOT CAUSE OF BUG:
      Panel initialization order was:
      1. _bind() registers PanelState listener (panel.js:151)
      2. _loadConfig() calls PanelState.init() (panel.js:283)
      3. init() CLEARS listeners array (panel-state.js:38)

      This meant the field-reset event listener was removed before it could
      ever fire, so badges never updated after reset.

      SOLUTION:
      Don't reset listeners array in init(). Listeners should persist across
      config reloads since they're part of the panel's lifecycle, not the
      config's lifecycle.

      This fixes the main bug where 'Changed' badge and reset button don't
      disappear after clicking reset. b29c3e

    • test: fix badge tests by triggering real input events

      The test was calling BaseHandler.handleChange() directly with a custom
      callback, which bypassed the panel.js callback that updates badges.

      Now the test triggers actual 'input' events which run the registered
      event handlers in color.js, which then call the panel.js callback that
      updates badges via FieldHandlers.updateBadges().

      This should fix the test timeouts and make badges appear correctly. 05d117

    • fix: call BaseHandler.handleChange directly in tests

      Instead of triggering 'input' event (which may not work due to
      event delegation or validation), call BaseHandler.handleChange()
      directly to update field state and badges.

      Changes:
      - Import BaseHandler module
      - In changeFieldValue(), call BaseHandler.handleChange() directly
      - This ensures badges are updated via the callback mechanism

      This should fix timeout errors in badge tests. 5d71c6

    • fix: move helper methods outside test suite

      Moved helper methods (_findColorField, _getBadgeState, etc.) outside
      the test suite object to prevent them from being executed as tests.

      Problem: Test framework was executing helper methods as tests,
      causing timeout errors because helpers have no 'done' callback.

      Solution: Created 'helpers' object outside test suite with methods:
      - findColorField()
      - getBadgeState()
      - changeFieldValue()
      - clickReset()

      All test methods now call helpers.methodName() instead of
      this._methodName().

      This reduces test count from 14 to 7 actual tests. c76626

    • fix: add panel opening step to badge reset tests

      Add initial test that opens Theme Editor panel before running
      badge tests. This fixes timeout errors that occurred because
      tests tried to find fields before panel was opened.

      Changes:
      - Add 'panel should be opened before tests run' as first test
      - Wait 500ms after opening for fields to render
      - Verify color fields are present before continuing

      This ensures panel is ready before badge manipulation tests run. f741fc

    • improve: copy only summary and failed tests to clipboard

      Change copy-results button to copy only essential information:
      - Summary statistics (passed/failed/total/pass rate)
      - Failed tests with detailed error messages
      - Success message if all tests passed

      Removed: Full list of passed tests (was too verbose)

      This makes it easier to share test results and focus on failures. e77bfb

    • test: add field badges reset functionality tests

      Add comprehensive test suite for badge visibility after field reset:
      - Test 1: Panel and fields initialization
      - Test 2: PanelState module availability
      - Test 3: Badge appears when field is changed
      - Test 4: Badge disappears after reset (MAIN TEST)
      - Test 5: Modified badge remains after reset
      - Test 6: PanelState listener system on field-reset event

      The main test (Test 4) verifies that:
      - Changed badge and reset button appear when field is modified
      - Both disappear after clicking reset button
      - Field state isDirty becomes false after reset

      This test will help identify why badges currently don't hide
      after reset and provide a regression test for the fix.

      Run tests: Add ?jstest=1 to any page URL a24da8

    • debug: add logging to notifyListeners and addListener

      Track listener registration and event notification to identify
      why field-reset event is not reaching panel.js listener. 204655

    • debug: add detailed logging for badge state investigation

      Add console logs to track:
      - setValue() calls with stack trace
      - Field state after reset in PanelState
      - Field state when updateFieldBadges is called

      This will help identify why badges don't hide after reset. d22d12

    • fix: prevent badges from staying after reset

      Remove $input.trigger('change') from updateFieldUIAfterReset() to prevent
      re-triggering field change handlers that set isDirty=true again.

      Problem:
      - Reset button clicked → PanelState sets isDirty=false
      - updateFieldUIAfterReset() called → triggers 'change' event
      - 'change' handler calls handleChange() → sets isDirty=true again
      - Badges remain visible because isDirty is still true

      Solution:
      - Remove $input.trigger('change') from updateFieldUIAfterReset()
      - CSS preview already updates via 'field-reset' event in panel.js:162
      - No duplicate change event needed

      Now badges correctly hide after reset. f90019

    • fix: define badge template as inline string instead of DOM selector

      The previous approach used jQuery to load template from DOM, but jQuery
      was not available in the module context. This caused badges to not render.

      Changes:
      - Define template as inline string in renderBadges() method
      - Remove template from panel.html (no longer needed)
      - No external dependencies required (jQuery not needed)

      This ensures badges will render correctly without dependency issues. fdfbb5

    • refactor: extract badge rendering to template

      Replace string concatenation in renderBadges() with underscore
      template for better maintainability and separation of concerns.

      Changes:
      - Add field-badges-template to panel.html with identical HTML
      - Update renderBadges() to use mageTemplate() with lazy loading
      - Reduce renderBadges() method from 30 lines to 20 lines
      - No functional changes, pure refactoring

      The template preserves all existing HTML structure, CSS classes,
      attributes, and behavior. Badges will continue to show/hide correctly
      based on field state (isDirty/isModified). 723721

    • Fix: Remove duplicate code block in ColorHandler.updateFieldUIAfterReset

      During the previous edit, a duplicate code block (38 lines) was accidentally
      left in the file, causing the method to execute twice. The second execution
      used 'this._resolvePaletteRef()' without handlerRef, causing the error to
      appear again.

      Removed duplicate lines 552-588:
      - Duplicate $wrapper, $input, $preview lookups
      - Duplicate isPaletteRef check
      - Duplicate _resolvePaletteRef call (without handlerRef)
      - Duplicate input updates

      The correct implementation (lines 514-550) remains, which properly uses
      (handlerRef || this)._resolvePaletteRef() for method access.

      Error that appeared due to duplicate:
      TypeError: this._resolvePaletteRef is not a function
      at ColorHandler.updateFieldUIAfterReset (color.js:565) 5e89ff

    • Fix: Pass handler reference to specialized methods for dual context access

      The previous .call(this) approach caused issues when specialized handlers
      needed to call their own methods. ColorHandler.updateFieldUIAfterReset()
      needs access to both:
      1. BaseHandler methods (_findFieldElement) via 'this' context
      2. ColorHandler methods (_resolvePaletteRef) via handler reference

      Solution: Pass both $field and handlerRef as additional parameters.

      Changes:
      - BaseHandler.handleFieldReset(): Pass $field and handler as params
      - ColorHandler.updateFieldUIAfterReset(): Accept $field and handlerRef
      - Use (handlerRef || this)._resolvePaletteRef() to access ColorHandler method

      Before:
      handler.updateFieldUIAfterReset.call(this, ...)
      → this._resolvePaletteRef() fails (BaseHandler has no _resolvePaletteRef)

      After:
      handler.updateFieldUIAfterReset.call(this, ..., $field, handler)
      → handlerRef._resolvePaletteRef() works ✅
      → this._findFieldElement() works ✅

      Error fixed:
      TypeError: this._resolvePaletteRef is not a function
      at ColorHandler.updateFieldUIAfterReset (color.js:522) 067e34

    • Fix: Use .call() to preserve BaseHandler context in specialized handlers

      When calling specialized handler's updateFieldUIAfterReset(), the 'this'
      context was pointing to the specialized handler object (e.g., ColorHandler)
      instead of BaseHandler. This caused 'this._findFieldElement is not a function'
      error because specialized handlers don't have BaseHandler methods.

      Solution: Use .call(this) to explicitly set 'this' context to BaseHandler
      when invoking specialized handler methods. This allows specialized handlers
      to access all BaseHandler methods through 'this'.

      Before:
      handler.updateFieldUIAfterReset(...)
      → this = ColorHandler ❌ (no _findFieldElement method)

      After:
      handler.updateFieldUIAfterReset.call(this, ...)
      → this = BaseHandler ✅ (has all required methods)

      Error fixed:
      TypeError: this._findFieldElement is not a function
      at ColorHandler.updateFieldUIAfterReset (color.js:502) 3f1f65

    • Fix: Normalize field type to uppercase for handler registry lookup

      The handler registry uses uppercase keys (COLOR, TEXT, etc.) but DOM
      data-type attributes are lowercase (color, text, etc.), causing handler
      lookup to fail. This resulted in ColorHandler not being called for
      reset operations, so palette references weren't resolved to HEX values.

      Changes:
      - Added .toUpperCase() normalization in BaseHandler.handleFieldReset()
      - Now works with any case: 'color', 'COLOR', 'Color'
      - Maintains backward compatibility

      Before: 'color' → no handler found → BaseHandler used
      After: 'color' → 'COLOR' → ColorHandler found → palette refs resolved

      This fixes the bug where reset button showed CSS variable names
      (--color-brand-amber-dark) instead of HEX colors (#a16207). 35dcab

    • feat: implement handler registry pattern for specialized field operations

      Problem: When reset button was clicked on color fields, BaseHandler's generic
      updateFieldUIAfterReset() was called instead of ColorHandler's specialized
      version. This caused palette references (--color-brand-amber-dark) to be
      written directly to input instead of being resolved to HEX values.

      Solution: Implement registry pattern for smart handler dispatching:

      Changes:
      1. field-handlers.js:
      - Added handlersByType registry mapping field types to handlers
      - Pass registry to BaseHandler.attachResetHandler()

      2. field-handlers/base.js:
      - Added _handlerRegistry property to store registry
      - Added _getHandlerForType() to lookup handlers by type
      - Updated handleFieldReset() to use specialized handlers:
      * Reads field type from data-type attribute
      * Gets specialized handler from registry
      * Calls handler.updateFieldUIAfterReset() if available
      * Falls back to base implementation if no specialized handler
      - Updated attachResetHandler() to accept and store registry

      Benefits:
      - Extensible: Easy to add specialized handlers for other field types
      - DRY: No code duplication
      - Type-safe: Explicit type → handler mapping
      - Future-proof: Can extend for validation, destroy, etc.

      Now when reset is clicked on color field:
      ✅ ColorHandler.updateFieldUIAfterReset() is called
      ✅ Palette refs are resolved to HEX via PaletteManager
      ✅ Correct color values are displayed in input

      Fixes: Reset button displaying CSS variable instead of HEX color dc8048

    • fix: initialize PaletteManager before rendering sections

      Problem: Color fields with palette references displayed #000000 because
      PaletteManager was only initialized when palette section was rendered.
      If user opened other sections first, ColorRenderer couldn't resolve colors.

      Solution: Initialize PaletteManager immediately after config is loaded,
      BEFORE rendering any sections. This ensures palette colors are always
      available for ColorRenderer.prepareData().

      Changes:
      - panel.js: Added PaletteManager.init() in _loadConfig()
      - panel.js: Added PaletteManager.init() in _loadConfigFromPublication()
      - Initialization happens before _renderSections() call
      - palette-section-renderer.js guard (if !PaletteManager.storeId) still works

      Now color fields with palette refs (e.g., --color-brand-amber-dark)
      correctly display their colors instead of #000000.

      Fixes: #000000 color display bug 536502

    • fix: simplify palette color resolution in ColorRenderer

      Replaced complex 4-strategy resolution (_resolvePaletteColor with CSS/Config fallbacks) with simple PaletteManager flat index lookup (_getPaletteHexFromMapping).

      Changes:
      - ColorRenderer now directly looks up colors in PaletteManager.palettes[cssVar]
      - Removed _resolvePaletteColorFromCss (CSS variable parsing)
      - Removed _resolvePaletteColorFromConfig (duplicate of new method)
      - Updated ColorHandler to use new _getPaletteHexFromMapping method
      - Updated tests to reflect new architecture

      Impact:
      - Code reduction: 76 net lines removed (157 deleted, 81 added)
      - Simpler logic: Single source of truth (PaletteManager)
      - More reliable: No timing issues with CSS loading
      - Easier to maintain: One method instead of three

      Fixes #000000 color display bug when palette refs are used in fields. 12ad33

    • fix: output only modified palette colors, not defaults

      Issue #10: Palette colors output regardless of actual changes

      Problem:
      - Default palette colors from theme.json were always injected into CSS
      - This happened in both generate() and generateFromValuesMap() methods
      - Result: Unnecessary CSS bloat with duplicate Breeze default values

      Changes:
      - Remove default palette injection from CssGenerator::generate()
      - Remove default palette injection from CssGenerator::generateFromValuesMap()
      - Only output palette colors that exist in DB (section '_palette')
      - Add explanatory comments about Breeze base styles

      Impact:
      - Palette colors now output ONLY when user modifies them
      - Reduces CSS size by not duplicating Breeze defaults
      - Default palette colors still available from Breeze base styles
      - Modified palette colors properly override defaults

      Result:
      - No changes made → no palette CSS output (empty :root {})
      - User changes palette color → only that color output
      - Works for both current values and publication history 4ed4fa

    • fix: respect 'Enable Theme Editor' setting for published CSS

      Issue #10: Custom styles added regardless of 'Enable Theme Editor' status

      Changes:
      - Add isEnabled() check to ThemeCssVariables::getInlineCssContent()
      - Published CSS now respects 'Enable Theme Editor' config setting
      - If disabled, no custom CSS is output to frontend
      - Updated shouldRender() documentation to clarify conditions

      Behavior:
      - Theme Editor disabled → getInlineCssContent() returns empty string
      - shouldRender() returns false → <style> tag not rendered
      - Theme Editor enabled → CSS generation works as before

      Impact:
      - Fixes issue where custom styles were loaded even when module disabled
      - Reduces frontend overhead when Theme Editor is not in use
      - Aligns published CSS behavior with draft CSS (both check isEnabled) 3e3787

    • fix(tests): remove self-closing slashes from void tags

      - Change <input .../> to <input ...> per Magento coding standard
      - Fixes 11 phpcs warnings in cascade-behavior-test.js
      - Self-closing syntax not allowed for HTML void elements 26cf07

    • refactor: eliminate code duplication (71 lines → 0)

      - Extract PublicationChangelogTrait for shared changelog methods
      * getPublicationChangelog(int $publicationId): array
      * buildValuesMapFromChangelog(array $changelog): array
      * Used by ConfigFromPublication and GetCss resolvers

      - Extract generateCssFromFields() method in CssGenerator
      * Eliminates 32-line duplication within CssGenerator class
      * Used for both draft and published CSS generation

      Result: 0.80% duplication eliminated (phpcpd clean) c82c9b

    • feat(field-reset): add individual field reset button

      - Add reset button (↺) after "Changed" badge when isDirty=true
      - Native confirm dialog: "Discard unsaved changes?"
      - Restore draft value (savedValue) and clear isDirty flag
      - Special handling for color fields with palette references
      - Support all field types: color, text, number, range, select, toggle, textarea

      Files changed:
      - field-renderers/base.js: renderBadges() with reset button HTML
      - _theme-editor-fields.less: .bte-field-reset-btn styles
      - panel-state.js: resetField(), getDraftValue(), event listener system
      - field-handlers/base.js: handleFieldReset(), updateFieldUIAfterReset()
      - field-handlers/color.js: palette ref resolution, Pickr updates
      - field-handlers.js: attach reset handler
      - panel.js: field-reset event handling 9f2b70

    • pickr.min.js: Fixed 404 error c5ff01

    • fix(tests): correct sectionCode expectation for palette changes

      - Update test to expect '_palette' instead of 'palette'
      - Backend uses '_palette' as special section identifier (see CssGenerator.php)
      - Update JSDoc comment to reflect actual implementation
      - Fixes test: 'should track dirty state when color changes'

      Expected: 100% pass rate (111/111 tests) 499e82

    • test(color-renderer): add comprehensive resolution tests

      Add 13 new tests for ColorRenderer palette reference resolution:

      1. Detect palette reference from value
      2. Handle regular HEX colors
      3. Use default value for invalid colors
      4. Fallback to black without valid default
      5. Resolve palette ref from PaletteManager config
      6. Resolve palette ref from CSS variables
      7. Fallback through resolution chain (CSS → Config → Default)
      8. Convert CSS RGB values to HEX
      9. Handle invalid CSS variables gracefully
      10. Resolve from PaletteManager config directly
      11. Preserve palette configuration in data
      12. Handle missing palette configuration
      13. Validate HEX color format strictly (7 test cases)

      Tests verify 3-level fallback resolution:
      CSS computed style → PaletteManager config → field default → black

      Total tests: 98 → 111 tests 37a785

    • test(cascade): add comprehensive cascade behavior tests

      Add 7 new tests for palette color cascade functionality:

      1. PaletteManager subscription in CssPreviewManager
      2. Finding fields with matching data-palette-ref
      3. Updating input values when palette changes
      4. Preserving data-palette-ref during cascade
      5. Setting/clearing is-palette-update flag
      6. Updating multiple fields with same palette ref
      7. Not affecting fields without palette ref

      These tests verify the cascade chain:
      PaletteManager.notify() → CssPreviewManager subscription →
      field updates → Pickr updates → flag handling

      Total tests: 91 → 98 tests ff1ad6

    • fix(palette-manager): reset dirtyColors and listeners on init

      - Add dirtyColors = {} reset in init() for test isolation
      - Add listeners = [] reset to prevent subscriber leaks between tests
      - Fixes failing test: 'should track dirty state when color changes'
      - Ensures each test starts with clean slate (no state from previous tests) 6b5752

    • refactor(tests): simplify dirty state test to synchronous execution

      - Remove unnecessary done() callback from 'should track dirty state' test
      - Convert from async to sync test (all operations are synchronous)
      - Clean up self reference (use this directly) dab18e

    • fix(color-popup): adjust close button position and z-index

      - Move close button from (8px, 8px) to (-2px, -2px) for better corner alignment
      - Increase z-index from 10 to 10005 to ensure visibility above all popup content cafdad

    • feat(cascade): propagate palette color changes to referencing fields

      - Subscribe CssPreviewManager to PaletteManager changes
      - Auto-update fields with data-palette-ref when palette color changes
      - Add is-palette-update flag to prevent reference removal during cascade
      - Update Pickr instances silently to avoid triggering change handlers
      - Cleanup both is-palette-selection and is-palette-update flags in base handler
      - Preserve data-palette-ref attributes throughout cascade chain d89240

    • feat(color-field): resolve palette references on initial render

      - Add 3-level fallback for palette reference resolution:
      1. CSS computed variables from document
      2. PaletteManager configuration (GraphQL data)
      3. Field default value
      - Add data-palette-ref attribute to input and trigger elements
      - Fix F5 refresh issue where palette refs showed as #000000
      - Preserve palette reference metadata in DOM for cascade updates 79571c

    • feat(backend): add palette reference support in CSS generation

      - Format palette references as CSS variables (var(--color-*))
      - Ensure palette variables are rendered before field variables
      - Support both direct HEX values and palette references in CssGenerator 9b3374

    • fix(publication-selector): update checkmark dynamically on status switch

      - Add updateCheckmarks() method to renderer
      - Call it after switching Draft/Published/Publication
      - Checkmark now updates immediately without page refresh 200037

breezefront / module-breeze-content-builder

2 days ago success
  • head
    • Fixed horizontal scroll on mobile caused by Price Label c12998

    • Added Max Width option for Banner and Horizontal Align for Columns, see #19 628cab

    • Improved featured product styles 0388fd

    • Added CSS class option for Newsletter component 37ce46

    • Featured product style improvement ccfea6

    • Show random product(s) when no product selected in component 7dd87a

    • Rewrite installer command to be independent e67955

    • Added installer 329c30

    • Do not force font-size for banner subtext 7117f2

    • Fixed empty preview in some cases 35b5ac

    • Allow to use negative margin values 1e7589

    • Added link item to menu, see #21 5277af

    • Prevent link navigation when clicking on component to edit it 645259

    • Fixed banner link overlapped by content, see #19 f90e70

    • Unfold tree when child component is selected for edit, see #22 0dcdc3

    • Fixed JS error $.widget is not a function 274c7a

    • Banner component rework, see #19 ab4e0a

    • Added light theme, see #20 f3d030

    • Fixed lost preview highlight after toggling editor for the page fc9b11

    • Fixed missing toolbar error (race condition) 4d7871

    • Improve preview initial load: single request instead of multiple b0edbc

    • Removed table border color style to use one from theme, see #16 a1647d

    • Added type and visibility columns to product selection modal, see #14 d6ccb4

    • Add opacity control to color picker, see #13 b2f50a

    • MCS fixes 0d2211

    • MCS fixes 4c0ad3

    • Split content-builder.js into component-factory, component-search, preview-bridge, version-history sub-modules 618e21

    • Improve panel and toolbar-integration: move html to templates 804324

    • Improve component-tree.js: move svg and html to template ba1a22

    • Fixed editing page from Content > Pages 3d1274

    • Use the same default padding for all layout components 467745

    • Rewrite ajax controllers using form_key ca6da6

    • Delete redundant requirejs-config d2ddbe

    • Use one generic renderer for simple components e2aa35

    • Sync store switcher with changes in Theme Editor, see #17 c9e9ac

    • Fixed color picker displaying off-screen 96ed98

    • New Collage component, see #18 7fad5f

    • Fixed palette init and color clearing d0e685

    • New Product Compare component, see #16 2fb3d1

    • Added config to use Inline SVG icon for Text component 5c8ead

    • New Featured Product component, see #14 7091ee

    • Fixed ButtonLockManager error on Magento 2.4.6, see #15 094014

    • Added Newsletter component, see #15 592edf

    • Added Divider component, see #13 c5cf0a

    • Text component: added icon config, see #12 e5ba47

    • Text component: added heading type config aebe4a

    • Added margins/paddings config for Columns and Tabs, see #10 669b9c

    • MCS fixes 0621d1

    • Fixed components tree jumping to top after making changes 8aebce

    • Added Tabs layout component, see #10 34efed

    • Force customers to use layout elements as root elements, see #9 12206f

    • MCS fixes 275435

    • Improved content status display and version history management 63e807

    • Added show/hide feature for components, closes #3 4b6510

    • Break admin styles into multiple files 87a61b

    • Added theme palette to color picker, see #4 6b30ed

    • Fixed placeholder replacing content on frontend for recently viewed/compared widget, closes #2 69218f

    • Show placeholder for Recently viewed/compared widgets in preview, closes #2 fedb28

    • Fixed unescaped output error 05815a

    • Added columns property for the Products component, closes #8 5feba3

    • Fixed old pages list in CMS pages dropdown when store change happens with closed Content Builder 373a10

    • Do not render technical preview attributes on frontend 2702bd

    • Improve preview centering logic when component selected/created 7d5ef0

    • Added native Magento widgets support e583f4

    • Fixed horizontal scrollbar on tablet/mobile when Columns component used f83897

    • Text component: change background color with color picker and added text color prop 695fd7

    • Added carousel type for the Products component a956f2

    • Products component improvements: category selector, SKU type, rendering 1faebf

    • Remove TinyMCE console warning 1e8aa3

    • Use TinyMCE for the Text component 255302

    • Image component improvements (loading, width, caption), closes #1 09566f

    • Show only store-specific CMS page in dropdown when multiple pages with the same URL exist ae625e

    • Fixed detected copy/paste 75bc4c

    • Update CMS pages dropdown when store view changed 2fb95a

    • Added components groups: layout and content 3e041d

    • Added new layout component: Row 8a192b

    • MCS fixes 09b4e1

    • First working prototype 3e4682

    • Add MVP development plan documentation 773ce1

    • Create module.xml 1c77ba

    • Create composer.json ac4f4f

    • Create registration.php 423e3c

    • Initial commit 2c6af9

swissup / argento-420shop

2 days ago success
  • head
    • Fix mobile footer collapsible list background color to match footer green c08cb5

    • Comment out min-height and aspect-ratio on jumbotron-image to remove blank space above slider b00ea7

    • Fix empty space on homepage - reduce jumbotron min-height from 448px to 200px, hide empty hr_frontPage boxes b283be

    • Add Dutch translations for product review prompt, out of stock source items, and requested quantity error

      - Be the first person to review this product. Share with others whether you like it or not.
      - There are no source items with the in stock status
      - THE REQUESTED QTY IS NOT AVAILABLE 2fcb33

swissup / theme-stigefabrikken-breeze

2 days ago error

swissup / stape-gtm

2 days ago success