diff --git a/CHANGELOG.md b/CHANGELOG.md index a709311..75c474d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [2.1.1] — 2026-08-10 + +### Changed +- Orange and yellow Desk status pills now share the published semantic warning color instead of using the brand accent for orange pills, keeping warning states consistent across indicators, pills, badges, and alerts + +### Fixed +- Switching Theme Studio preview scenes or opening a full settings section no longer auto-opens a contextual inspector; inspectors now open only after deliberately selecting an editable preview element +- Published success, warning, error, and info colors now style the corresponding Desk status pills and badges, including cyan/info variants and accessible color-blind palette behavior + ## [2.1.0] — 2026-08-07 — Icon Rail Sidebar ### Added diff --git a/solvronix_desk/__init__.py b/solvronix_desk/__init__.py index 9aa3f90..58039f5 100644 --- a/solvronix_desk/__init__.py +++ b/solvronix_desk/__init__.py @@ -1 +1 @@ -__version__ = "2.1.0" +__version__ = "2.1.1" diff --git a/solvronix_desk/hooks.py b/solvronix_desk/hooks.py index 2ad6b7b..9f3e2be 100644 --- a/solvronix_desk/hooks.py +++ b/solvronix_desk/hooks.py @@ -7,7 +7,7 @@ app_license = "MIT" app_color = "#E8610A" app_icon = "octicon octicon-paintcan" -app_version = "2.1.0" +app_version = "2.1.1" required_apps = [] @@ -18,7 +18,7 @@ # ── AUTHENTICATED DESK ASSETS ────────────────────────────────────────────────── # Query versions are bumped whenever an asset changes to invalidate browser cache. app_include_css = [ - "/assets/solvronix_desk/css/solvronix_desk.css?v=52", + "/assets/solvronix_desk/css/solvronix_desk.css?v=53", "/assets/solvronix_desk/css/sidebar.css?v=31", "/assets/solvronix_desk/css/command_palette.css?v=4", "/assets/solvronix_desk/css/smart_home.css?v=7", diff --git a/solvronix_desk/public/css/solvronix_desk.css b/solvronix_desk/public/css/solvronix_desk.css index 277976f..3a2476f 100644 --- a/solvronix_desk/public/css/solvronix_desk.css +++ b/solvronix_desk/public/css/solvronix_desk.css @@ -79,6 +79,10 @@ textarea, --st-text-primary: #1C2333; --st-text-muted: #6B7280; --st-border: #E5E7EB; + --st-success: #238A57; + --st-warning: #C87818; + --st-error: #C33D43; + --st-info: #2D72B8; --st-list-row-bg: #FFFFFF; --st-list-row-hover: #F8F9FA; --st-bg: var(--st-page-bg); /* backwards-compat alias */ @@ -809,13 +813,15 @@ html[data-theme="dark"] .desk-page > .page-head .st-breadcrumb { padding: 3px 10px !important; } .indicator-pill.green, -.badge-success { background: rgba(34,197,94,0.12) !important; color: #16a34a !important; } +.badge-success { --indicator-color: var(--st-success); background: color-mix(in srgb,var(--st-success) 12%,transparent) !important; color: var(--st-success) !important; } .indicator-pill.red, -.badge-danger { background: rgba(239,68,68,0.12) !important; color: #dc2626 !important; } -.indicator-pill.orange { background: var(--st-primary-light) !important; color: var(--st-accent) !important; } +.badge-danger { --indicator-color: var(--st-error); background: color-mix(in srgb,var(--st-error) 12%,transparent) !important; color: var(--st-error) !important; } +.indicator-pill.orange, .indicator-pill.yellow, -.badge-warning { background: rgba(234,179,8,0.12) !important; color: #ca8a04 !important; } -.indicator-pill.blue { background: rgba(59,130,246,0.12) !important; color: #2563eb !important; } +.badge-warning { --indicator-color: var(--st-warning); background: color-mix(in srgb,var(--st-warning) 12%,transparent) !important; color: var(--st-warning) !important; } +.indicator-pill.blue, +.indicator-pill.cyan, +.badge-info { --indicator-color: var(--st-info); background: color-mix(in srgb,var(--st-info) 12%,transparent) !important; color: var(--st-info) !important; } .indicator-pill.purple { background: rgba(139,92,246,0.12) !important; color: #7c3aed !important; } .indicator-pill.gray, .indicator-pill.grey { background: rgba(107,114,128,0.1) !important; color: #4b5563 !important; } diff --git a/solvronix_desk/solvronix_desk/page/theme_studio/theme_studio.js b/solvronix_desk/solvronix_desk/page/theme_studio/theme_studio.js index cc07245..be1478e 100644 --- a/solvronix_desk/solvronix_desk/page/theme_studio/theme_studio.js +++ b/solvronix_desk/solvronix_desk/page/theme_studio/theme_studio.js @@ -679,6 +679,14 @@ solvronix_desk.ThemeStudio = class ThemeStudio { $outputs.each(function () { $(this).text(value + ($(this).data("unit") || "")); }); } + _use_custom_status_palette(key) { + if (["success_color", "warning_color", "error_color", "info_color"].indexOf(key) === -1) return false; + if (this.config.colorblind_palette === "Default") return false; + this.config.colorblind_palette = "Default"; + this._sync_setting_inputs("colorblind_palette"); + return true; + } + _effective_color_values(visual) { var c = visual || {}; var values = {}; @@ -1477,27 +1485,21 @@ solvronix_desk.ThemeStudio = class ThemeStudio { } _activate_preview_scene(scene) { - this._clear_workspace_selection(); + /* Scene tabs only change the preview. Contextual settings should open + after the user deliberately selects an editable preview element. */ + this._clear_workspace_selection(false); + this.selected_inspector = null; + this.selected_chart_preview_element = null; + this._render_inspector(); this.$root.find("[data-preview-scene]").removeClass("active").filter('[data-preview-scene="' + scene + '"]').addClass("active"); this.$preview.attr("data-scene", scene); this.$root.toggleClass("is-workspace-preview", scene === "workspace"); this.$preview.find(".sts-scene").removeClass("active").filter('[data-scene="' + scene + '"]').addClass("active"); - if (scene === "workspace") { - this.selected_inspector = null; - this._render_inspector(); - return; - } + if (scene === "workspace") return; if (scene === "charts") { if (!this.selected_chart_preview_kind) this.selected_chart_preview_kind = "line"; return; } - if (this.selected_inspector === "charts.chart") { - this.selected_inspector = null; - this.selected_chart_preview_element = null; - } - var defaults = { dashboard: "dashboard.heading", form: "form.card", table: "table.grid", login: "login.background" }; - var element = this.$preview.find('[data-inspector="' + defaults[scene] + '"]:visible').first()[0]; - if (element) this._select_inspector(defaults[scene], element); } _workspace_scene_html() { @@ -2090,6 +2092,9 @@ solvronix_desk.ThemeStudio = class ThemeStudio { self.$root.find("[data-section-tab]").removeClass("active").filter('[data-section-tab="' + section + '"]').addClass("active"); self.$root.find(".sts-control-panel").removeClass("active").filter('[data-section="' + section + '"]').addClass("active"); self.$root.find('[data-section="' + section + '"]')[0].scrollIntoView({ behavior: "smooth", block: "start" }); + self._clear_workspace_selection(false); + self.selected_inspector = null; + self._render_inspector(); }); this.$root.on("input change", "[data-chart-path]", function () { var $input = $(this), path = $input.data("chart-path"); @@ -2150,6 +2155,7 @@ solvronix_desk.ThemeStudio = class ThemeStudio { } else { self.config[key] = this.value; } + self._use_custom_status_palette(key); if (key === "chart_background" || key === "chart_palette") { self.config.chart_system_version = self._chart_schema().version || 1; self.config.chart_defaults = self.config.chart_defaults || {}; @@ -2172,6 +2178,7 @@ solvronix_desk.ThemeStudio = class ThemeStudio { } self._checkpoint(); self.config[key] = value; + self._use_custom_status_palette(key); self._sync_setting_inputs(key, this); self.changed(); }); @@ -2221,7 +2228,6 @@ solvronix_desk.ThemeStudio = class ThemeStudio { }); this.$root.on("click", "[data-preview-scene]", function () { self._activate_preview_scene($(this).data("preview-scene")); - if ($(this).data("preview-scene") === "charts") self._select_chart_preview(self.selected_chart_preview_kind || "line"); }); this.$root.on("change", "#sts-workspace-select", function () { self._select_workspace(this.value); diff --git a/solvronix_desk/theme_engine.py b/solvronix_desk/theme_engine.py index d61296c..47797ae 100644 --- a/solvronix_desk/theme_engine.py +++ b/solvronix_desk/theme_engine.py @@ -1237,10 +1237,26 @@ def render_css(config, enabled=True): color: var(--st-sidebar-hover-text) !important; stroke: currentColor !important; }} -.indicator.green,.alert-success {{ --indicator-color: var(--st-success); }} -.indicator.orange,.alert-warning {{ --indicator-color: var(--st-warning); }} -.indicator.red,.alert-danger {{ --indicator-color: var(--st-error); }} -.indicator.blue,.alert-info {{ --indicator-color: var(--st-info); }} +.indicator.green,.indicator-pill.green,.badge-success,.alert-success {{ --indicator-color: var(--st-success); }} +.indicator.orange,.indicator.yellow,.indicator-pill.orange,.indicator-pill.yellow,.badge-warning,.alert-warning {{ --indicator-color: var(--st-warning); }} +.indicator.red,.indicator-pill.red,.badge-danger,.alert-danger {{ --indicator-color: var(--st-error); }} +.indicator.blue,.indicator.cyan,.indicator-pill.blue,.indicator-pill.cyan,.badge-info,.alert-info {{ --indicator-color: var(--st-info); }} +.indicator-pill.green,.badge-success {{ + background-color: color-mix(in srgb,var(--st-success) 12%,transparent) !important; + color: var(--st-success) !important; +}} +.indicator-pill.orange,.indicator-pill.yellow,.badge-warning {{ + background-color: color-mix(in srgb,var(--st-warning) 12%,transparent) !important; + color: var(--st-warning) !important; +}} +.indicator-pill.red,.badge-danger {{ + background-color: color-mix(in srgb,var(--st-error) 12%,transparent) !important; + color: var(--st-error) !important; +}} +.indicator-pill.blue,.indicator-pill.cyan,.badge-info {{ + background-color: color-mix(in srgb,var(--st-info) 12%,transparent) !important; + color: var(--st-info) !important; +}} body:has(.for-login),body:has(.for-forgot),body:has(.for-email-login) {{ background-color: var(--st-login-bg) !important; {bg_image} background-position:center!important;background-size:cover!important; diff --git a/tests/test_theme_engine.py b/tests/test_theme_engine.py index 3925aed..02763f6 100644 --- a/tests/test_theme_engine.py +++ b/tests/test_theme_engine.py @@ -10,6 +10,7 @@ ROOT = Path(__file__).resolve().parents[1] ENGINE_PATH = ROOT / "solvronix_desk" / "theme_engine.py" +DESK_CSS_PATH = ROOT / "solvronix_desk" / "public" / "css" / "solvronix_desk.css" class FrappeStub(types.ModuleType): @@ -91,6 +92,25 @@ def test_renderer_outputs_complete_runtime_tokens(self): ) self.assertNotIn("#st-top-toolbar { height: var(--st-header-height) !important; position:sticky", css) + def test_actual_desk_status_pills_consume_semantic_theme_colors(self): + static_css = DESK_CSS_PATH.read_text(encoding="utf-8") + runtime_css = ENGINE.render_css(ENGINE.DEFAULT_CONFIG) + selectors_by_token = { + "--st-success": (".indicator-pill.green", ".badge-success"), + "--st-warning": (".indicator-pill.orange", ".indicator-pill.yellow", ".badge-warning"), + "--st-error": (".indicator-pill.red", ".badge-danger"), + "--st-info": (".indicator-pill.blue", ".indicator-pill.cyan", ".badge-info"), + } + + for token, selectors in selectors_by_token.items(): + for selector in selectors: + self.assertIn(selector, static_css) + self.assertIn(selector, runtime_css) + for css in (static_css, runtime_css): + self.assertIn(f"--indicator-color: var({token})", css) + self.assertIn(f"color: var({token}) !important", css) + self.assertIn(f"color-mix(in srgb,var({token}) 12%,transparent)", css) + def test_non_sticky_toolbar_is_still_removed_from_flex_layout(self): config = dict(ENGINE.DEFAULT_CONFIG) config["sticky_navbar"] = False diff --git a/tests/test_workspace_editorjs_popover_css.py b/tests/test_workspace_editorjs_popover_css.py index 40e81ed..79c76ef 100644 --- a/tests/test_workspace_editorjs_popover_css.py +++ b/tests/test_workspace_editorjs_popover_css.py @@ -39,7 +39,7 @@ def test_block_with_open_editorjs_popover_is_promoted_before_focus(self): def test_workspace_css_cache_is_bumped(self): hooks = HOOKS.read_text(encoding="utf-8") - self.assertIn("/assets/solvronix_desk/css/solvronix_desk.css?v=52", hooks) + self.assertIn("/assets/solvronix_desk/css/solvronix_desk.css?v=53", hooks) self.assertIn("/assets/solvronix_desk/js/solvronix_desk.js?v=63", hooks) diff --git a/tests/theme_studio_behavior.test.js b/tests/theme_studio_behavior.test.js index c0c7c26..a49e3b8 100644 --- a/tests/theme_studio_behavior.test.js +++ b/tests/theme_studio_behavior.test.js @@ -111,7 +111,7 @@ function bindThemeStudio(studio) { const stageHandlers = {}; const chain = { length: 1, - 0: {}, + 0: { scrollIntoView() {} }, off() { return this; }, on(event, selector, handler) { if (typeof selector === "function") handler = selector; @@ -252,6 +252,18 @@ test("color-blind palette updates semantic preview colors", () => { assert.equal(resolved.info_color, "#56B4E9"); }); +test("editing a semantic color switches the status palette to custom colors", () => { + const studio = loadThemeStudio(); + const synced = []; + studio.config = { colorblind_palette: "Deuteranopia" }; + studio._sync_setting_inputs = (key) => synced.push(key); + + assert.equal(studio._use_custom_status_palette("warning_color"), true); + assert.equal(studio.config.colorblind_palette, "Default"); + assert.deepEqual(synced, ["colorblind_palette"]); + assert.equal(studio._use_custom_status_palette("card_background"), false); +}); + function installChartState(studio) { studio.state.chart_schema = { version: 1, @@ -2781,7 +2793,7 @@ test("workspace lifecycle clears stale selections at every document boundary", a assert.deepEqual(order, [["clear", false], ["selected", null], ["render"]]); }); - await t.test("scene changes clear first and Workspace has no default inspector", () => { + await t.test("scene tabs close the current inspector without opening a default", () => { const studio = loadThemeStudio(); const order = []; const bindings = bindThemeStudio(studio); @@ -2809,7 +2821,16 @@ test("workspace lifecycle clears stale selections at every document boundary", a dashboardButton.__query = Object.assign({}, bindings.chain, { 0: dashboardButton }); bindings.rootHandlers["click|[data-preview-scene]"].call(dashboardButton); assert.equal(order[0], "clear"); - assert.deepEqual(order.find((entry) => Array.isArray(entry) && entry[0] === "select"), ["select", "dashboard.heading"]); + assert.equal(studio.selected_inspector, null); + assert.equal(order.some((entry) => Array.isArray(entry) && entry[0] === "select"), false); + + order.length = 0; + studio._select_chart_preview = () => order.push(["select", "chart"]); + const chartsButton = { __data: { "preview-scene": "charts" } }; + chartsButton.__query = Object.assign({}, bindings.chain, { 0: chartsButton }); + bindings.rootHandlers["click|[data-preview-scene]"].call(chartsButton); + assert.equal(studio.selected_inspector, null); + assert.equal(order.some((entry) => Array.isArray(entry) && entry[0] === "select"), false); }); await t.test("iframe load clears before its first guard and iframe error clears before state", () => { @@ -2864,6 +2885,23 @@ test("workspace lifecycle clears stale selections at every document boundary", a }); }); +test("opening a full settings section closes the contextual inspector", () => { + const studio = loadThemeStudio(); + studio.selected_inspector = "dashboard.metrics"; + const order = []; + studio._clear_workspace_selection = (closeInspector) => order.push(["clear", closeInspector]); + studio._render_inspector = () => order.push(["render", studio.selected_inspector]); + const bindings = bindThemeStudio(studio); + const button = { __data: { "open-control-section": "colors" } }; + button.__query = Object.assign({}, bindings.chain, { 0: button }); + + bindings.rootHandlers["click|[data-open-control-section]"].call(button); + + assert.equal(studio.active_section, "colors"); + assert.equal(studio.selected_inspector, null); + assert.deepEqual(order, [["clear", false], ["render", null]]); +}); + test("workspace reanchor wiring covers successful movement and coalesces rapid sources", () => { const studio = loadThemeStudio(); let schedules = 0;