Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion solvronix_desk/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.1.0"
__version__ = "2.1.1"
4 changes: 2 additions & 2 deletions solvronix_desk/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Expand All @@ -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",
Expand Down
16 changes: 11 additions & 5 deletions solvronix_desk/public/css/solvronix_desk.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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; }
Expand Down
34 changes: 20 additions & 14 deletions solvronix_desk/solvronix_desk/page/theme_studio/theme_studio.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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 || {};
Expand All @@ -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();
});
Expand Down Expand Up @@ -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);
Expand Down
24 changes: 20 additions & 4 deletions solvronix_desk/theme_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions tests/test_theme_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/test_workspace_editorjs_popover_css.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
44 changes: 41 additions & 3 deletions tests/theme_studio_behavior.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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;
Expand Down