From 212b627d7652d2851ae30285abacdd7a4556a7bb Mon Sep 17 00:00:00 2001 From: benjsmith Date: Tue, 29 Sep 2026 22:00:01 +0000 Subject: [PATCH 1/4] fix(agents): sync okstratr embed with Switchbay light/dark theme Observer CSS hard-codes Omarchy dark tokens on :root. Under same-document embed those bleed into Switchbay light chrome (dark desk/DAG panels). Remap okstratr vars on the proxied mount from :root[data-theme], so Agents matches SPA tokens and the footer theme toggle flips without reload. Signed-off-by: benjsmith --- frontend/src/index.css | 85 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index 72c1754..031b09b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -9267,3 +9267,88 @@ body.sy-embed-hosting { font-family: ui-monospace, Menlo, monospace; font-size: 0.85em; } + +/* ── Agents / okstratr theme sync (light + dark) ─────────────────── + * Observer CSS hard-codes Omarchy dark tokens on :root. Same-document + * embed inherits the host document, so under Switchbay light chrome + * --panel/--recessed/--chip stay dark while --bg/--text flip light + * (Switchbay :root[data-theme] wins on those shared names) → dark-on- + * light desk/DAG. Remap okstratr tokens on the proxied mount so both + * themes match SPA chrome. :root[data-theme] flips live with the + * footer toggle (no reload); no Attention / network_org_hooks here. + */ +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html { + --bg: #f8f9fb; + --panel: #ffffff; + --recessed: #f0f2f5; + --border: #dfe2e7; + --divider: #dfe2e7; + --text: #1a1d22; + --fg: #1a1d22; + --muted: #5a6068; + --accent: #3d6fd9; + --ok: #3d8b4f; + --warn: #b8860b; + --bad: #c44; + --purple: #7c5cbf; + --chip: #e8ebf0; + --focus: #3d6fd933; + --focus-border: #3d6fd9; + color-scheme: light; + background: var(--bg) !important; + color: var(--text) !important; +} +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .top, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .panel, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .main, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .rail, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .config, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .desk-card, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .card, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .side, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .board { + border-color: var(--border) !important; +} +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn { + background: var(--chip) !important; + border-color: var(--border) !important; + color: var(--text) !important; +} +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.primary, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.mini.active, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .kind-chip.active { + background: var(--focus) !important; + border-color: var(--accent) !important; + color: var(--fg) !important; +} + +:root[data-theme="dark"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html { + /* Keep okstratr desk tokens coherent with Switchbay dark SPA. */ + --bg: #0f1115; + --panel: #161a21; + --recessed: #14181f; + --border: #232830; + --divider: #232830; + --text: #e6e8eb; + --fg: #e6e8eb; + --muted: #9aa0a8; + --accent: #7aa2f7; + --ok: #9ece6a; + --warn: #e0af68; + --bad: #f7768e; + --purple: #bb9af7; + --chip: #1f242c; + --focus: #7aa2f744; + --focus-border: #7aa2f7; + color-scheme: dark; + background: var(--bg) !important; + color: var(--text) !important; +} + +:root[data-theme="light"] .sy-agents, +:root[data-theme="light"] .sy-agents-section, +:root[data-theme="light"] .sy-agents-row { + background: var(--bg-modal, #fff); + color: var(--text, #1a1d22); +} From d88f9c7a22239a2d40e510a889ca19039bc87b40 Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 3 Oct 2026 08:43:17 +0000 Subject: [PATCH 2/4] fix(agents): mint accents + preserve status chip colours Match Switchbay accents (#2f9874 / #6be8b3) on the okstratr mount. Stop light-theme .chip !important from wiping .chip.running / .chip.idle. Canvas DAG labels need okstratr fix/observer-theme-canvas-labels (#30). Signed-off-by: benjsmith --- frontend/src/index.css | 29 +++++++++++++++------ tests/unit/test_agents_theme_sync_css.py | 32 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_agents_theme_sync_css.py diff --git a/frontend/src/index.css b/frontend/src/index.css index 031b09b..0ad1fdd 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -9286,14 +9286,14 @@ body.sy-embed-hosting { --text: #1a1d22; --fg: #1a1d22; --muted: #5a6068; - --accent: #3d6fd9; + --accent: #2f9874; /* Switchbay light mint */ --ok: #3d8b4f; --warn: #b8860b; --bad: #c44; --purple: #7c5cbf; --chip: #e8ebf0; - --focus: #3d6fd933; - --focus-border: #3d6fd9; + --focus: #2f987433; + --focus-border: #2f9874; color-scheme: light; background: var(--bg) !important; color: var(--text) !important; @@ -9309,12 +9309,25 @@ body.sy-embed-hosting { :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .board { border-color: var(--border) !important; } -:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip, +/* Neutral chips/buttons only — do not wipe .chip.running / .chip.idle status colours. */ +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip:not(.running):not(.idle), :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn { background: var(--chip) !important; border-color: var(--border) !important; color: var(--text) !important; } +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip.running, +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip.idle { + background: var(--chip) !important; +} +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip.running { + color: var(--ok) !important; + border-color: #3d5a3a !important; +} +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .chip.idle { + color: var(--warn) !important; + border-color: var(--border) !important; +} :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.primary, :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.mini.active, :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .kind-chip.active { @@ -9324,7 +9337,7 @@ body.sy-embed-hosting { } :root[data-theme="dark"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html { - /* Keep okstratr desk tokens coherent with Switchbay dark SPA. */ + /* Keep okstratr desk tokens coherent with Switchbay dark SPA (mint accent). */ --bg: #0f1115; --panel: #161a21; --recessed: #14181f; @@ -9333,14 +9346,14 @@ body.sy-embed-hosting { --text: #e6e8eb; --fg: #e6e8eb; --muted: #9aa0a8; - --accent: #7aa2f7; + --accent: #6be8b3; /* Switchbay dark mint */ --ok: #9ece6a; --warn: #e0af68; --bad: #f7768e; --purple: #bb9af7; --chip: #1f242c; - --focus: #7aa2f744; - --focus-border: #7aa2f7; + --focus: #6be8b344; + --focus-border: #6be8b3; color-scheme: dark; background: var(--bg) !important; color: var(--text) !important; diff --git a/tests/unit/test_agents_theme_sync_css.py b/tests/unit/test_agents_theme_sync_css.py new file mode 100644 index 0000000..87b53ae --- /dev/null +++ b/tests/unit/test_agents_theme_sync_css.py @@ -0,0 +1,32 @@ +"""Lock Agents/okstratr theme-sync CSS: Switchbay mint accents + status chips.""" + +from __future__ import annotations + +from pathlib import Path + +CSS = Path(__file__).resolve().parents[2] / "frontend" / "src" / "index.css" + + +def _okstratr_theme_block() -> str: + text = CSS.read_text() + start = text.index("Agents / okstratr theme sync") + # Stop before the built-in Agents light block that follows. + end = text.index(":root[data-theme=\"light\"] .sy-agents", start) + return text[start:end] + + +def test_accents_match_switchbay_mint(): + block = _okstratr_theme_block() + assert "--accent: #2f9874" in block + assert "--accent: #6be8b3" in block + assert "#3d6fd9" not in block + assert "#7aa2f7" not in block + + +def test_light_chip_rule_preserves_running_and_idle(): + block = _okstratr_theme_block() + assert ".chip:not(.running):not(.idle)" in block + assert ".chip.running" in block + assert "color: var(--ok)" in block + assert ".chip.idle" in block + assert "color: var(--warn)" in block From dcc737bf217f83845682280d52ed8483f046899b Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 3 Oct 2026 09:04:15 +0000 Subject: [PATCH 3/4] fix(agents): pin standing desk badge to idle amber Placeholder rows use .desk-obj--standing, not .chip.idle. Keep that badge on --warn in light and dark so a blanket color: var(--text) cannot wash it gray. Signed-off-by: benjsmith --- frontend/src/index.css | 9 +++++++ tests/unit/test_agents_theme_sync_css.py | 33 +++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 0ad1fdd..c28517e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -9328,6 +9328,10 @@ body.sy-embed-hosting { color: var(--warn) !important; border-color: var(--border) !important; } +/* Standing placeholder badge (.desk-obj--standing) uses idle amber, not --text/--muted. */ +:root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .desk-obj--standing { + color: var(--warn) !important; +} :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.primary, :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .btn.mini.active, :root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .kind-chip.active { @@ -9359,6 +9363,11 @@ body.sy-embed-hosting { color: var(--text) !important; } +/* Dark: same standing badge amber. Do not inherit the mount's --text. */ +:root[data-theme="dark"] .sy-proxied-skill[data-kind="okstratr"] .sy-proxied-skill-html .desk-obj--standing { + color: var(--warn) !important; +} + :root[data-theme="light"] .sy-agents, :root[data-theme="light"] .sy-agents-section, :root[data-theme="light"] .sy-agents-row { diff --git a/tests/unit/test_agents_theme_sync_css.py b/tests/unit/test_agents_theme_sync_css.py index 87b53ae..a6d1409 100644 --- a/tests/unit/test_agents_theme_sync_css.py +++ b/tests/unit/test_agents_theme_sync_css.py @@ -11,7 +11,7 @@ def _okstratr_theme_block() -> str: text = CSS.read_text() start = text.index("Agents / okstratr theme sync") # Stop before the built-in Agents light block that follows. - end = text.index(":root[data-theme=\"light\"] .sy-agents", start) + end = text.index(':root[data-theme="light"] .sy-agents', start) return text[start:end] @@ -30,3 +30,34 @@ def test_light_chip_rule_preserves_running_and_idle(): assert "color: var(--ok)" in block assert ".chip.idle" in block assert "color: var(--warn)" in block + + +def _rule_body(block: str, selector_needle: str) -> str: + idx = block.index(selector_needle) + return block[idx:block.index("}", idx)] + + +def test_standing_badge_stays_warn_not_text(): + """Placeholder .desk-obj--standing is idle amber in light and dark. + + A blanket color: var(--text) must not wash the standing badge gray. + """ + block = _okstratr_theme_block() + light = _rule_body( + block, + ':root[data-theme="light"] .sy-proxied-skill[data-kind="okstratr"] ' + ".sy-proxied-skill-html .desk-obj--standing", + ) + dark = _rule_body( + block, + ':root[data-theme="dark"] .sy-proxied-skill[data-kind="okstratr"] ' + ".sy-proxied-skill-html .desk-obj--standing", + ) + for rule in (light, dark): + assert "color: var(--warn)" in rule + assert "var(--text)" not in rule + assert "var(--muted)" not in rule + # Neutral chip wash still excludes status chips (idle amber / running green). + neutral = _rule_body(block, ".chip:not(.running):not(.idle)") + assert "desk-obj--standing" not in neutral + assert ".chip.idle" not in neutral.split("{", 1)[0] From 987207278d1f4aa9186faf146c6223484b046b67 Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 3 Oct 2026 09:14:57 +0000 Subject: [PATCH 4/4] fix(test): read agents theme CSS as utf-8 Windows CI uses cp1252 for Path.read_text() and dies on the UTF-8 arrow in index.css (byte 0x90). Assertions are unchanged. Signed-off-by: benjsmith --- tests/unit/test_agents_theme_sync_css.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_agents_theme_sync_css.py b/tests/unit/test_agents_theme_sync_css.py index a6d1409..493d2d1 100644 --- a/tests/unit/test_agents_theme_sync_css.py +++ b/tests/unit/test_agents_theme_sync_css.py @@ -8,7 +8,7 @@ def _okstratr_theme_block() -> str: - text = CSS.read_text() + text = CSS.read_text(encoding="utf-8") start = text.index("Agents / okstratr theme sync") # Stop before the built-in Agents light block that follows. end = text.index(':root[data-theme="light"] .sy-agents', start)