↗ вкладка
@@ -937,7 +992,11 @@ def render_page() -> str:
async function refreshLimits() {{
try {{
const r = await fetch('/limits', {{cache: 'no-store'}});
- if (r.ok) document.getElementById('limits').innerHTML = await r.text();
+ if (r.ok) {{
+ document.getElementById('gear-home').appendChild(gear); // иначе innerHTML уничтожит ⚙
+ document.getElementById('limits').innerHTML = await r.text();
+ placeGear();
+ }}
}} catch (e) {{}}
}}
setInterval(refreshLimits, 15000); // «сброс через …» тоже обновляется
@@ -1015,7 +1074,37 @@ def render_page() -> str:
document.addEventListener('keydown', e => {{ if (e.key === 'Escape') closeMsg(); }});
setInterval(loadMsg, 2000);
- // тема могла смениться — раз в минуту перезагружаем страницу (если сообщение не открыто)
+ // ---- фон панели на этом устройстве ----
+ const gear = document.getElementById('gear'), bgrow = document.getElementById('bgrow');
+ const bgCustom = document.getElementById('bg-custom');
+ function placeGear() {{
+ const slot = document.querySelector('#limits .gear-slot');
+ (slot || document.getElementById('gear-home')).appendChild(gear);
+ }}
+ function saveBg(hex) {{
+ if (!applyBg(hex)) return;
+ bgCustom.value = hex.toLowerCase();
+ try {{ localStorage.setItem('{BG_STORAGE_KEY}', hex.toLowerCase()); }} catch (e) {{}}
+ }}
+ gear.addEventListener('click', e => {{ e.stopPropagation(); bgrow.classList.toggle('open'); }});
+ bgrow.addEventListener('click', e => {{
+ e.stopPropagation();
+ const sw = e.target.closest('.sw');
+ if (sw) saveBg(sw.dataset.bg);
+ }});
+ bgCustom.addEventListener('input', () => saveBg(bgCustom.value));
+ document.getElementById('bg-reset').addEventListener('click', () => {{
+ applyBg(null);
+ bgCustom.value = '{DEFAULT_THEME["bg"]}';
+ try {{ localStorage.removeItem('{BG_STORAGE_KEY}'); }} catch (e) {{}}
+ }});
+ try {{
+ const saved = localStorage.getItem('{BG_STORAGE_KEY}');
+ if (panelColors(saved)) bgCustom.value = saved;
+ }} catch (e) {{}}
+ placeGear();
+
+ // раз в минуту перезагружаем страницу (если сообщение не открыто) — подхватить новую версию службы
setInterval(() => {{ if (!cur) location.reload(); }}, 60000);
"""
@@ -1238,7 +1327,8 @@ def render_limits() -> str:
if state["usage_updated"]:
upd = datetime.fromtimestamp(state["usage_updated"]).strftime("%H:%M:%S")
stale = state["usage_error"] is not None # последнее обновление не удалось — показаны старые цифры
- parts.append(f'')
+ parts.append(f'')
return "".join(parts)
diff --git a/tests/test_appearance.py b/tests/test_appearance.py
new file mode 100644
index 0000000..0f2bfd1
--- /dev/null
+++ b/tests/test_appearance.py
@@ -0,0 +1,61 @@
+import json
+import os
+import shutil
+import subprocess
+import sys
+import time
+import unittest
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "files"))
+import claude_agents as ca # noqa: E402
+
+
+class Page(unittest.TestCase):
+ def test_page_applies_saved_bg_before_body(self):
+ html = ca.render_page()
+ body = html.index("")
+ self.assertLess(html.index("claude-panel-bg"), body)
+ self.assertIn(ca.CLIENT_COLORS_JS, html)
+ self.assertIn("try {", html[:body])
+
+ def test_page_has_bg_controls(self):
+ html = ca.render_page()
+ for marker in ('id="bgctl"', 'id="gear"', 'id="bgrow"', 'type="color"', 'id="bg-reset"'):
+ self.assertIn(marker, html)
+ self.assertEqual(ca.BG_PRESETS, ("#002b36", "#1e1e1e", "#000000", "#2b2b3a", "#fdf6e3"))
+ for hex_ in ca.BG_PRESETS:
+ self.assertIn(f'data-bg="{hex_}"', html)
+ limits_end = html.index('
') + len('
') + len(ca.render_limits())
+ self.assertGreater(html.index('id="bgctl"'), limits_end)
+
+
+class Limits(unittest.TestCase):
+ def setUp(self):
+ self.saved = dict(ca.state)
+
+ def tearDown(self):
+ ca.state.clear()
+ ca.state.update(self.saved)
+
+ def test_limits_foot_has_gear_slot(self):
+ ca.state["usage_updated"], ca.state["usage_error"] = time.time(), None
+ self.assertIn('class="gear-slot"', ca.render_limits())
+
+
+@unittest.skipUnless(shutil.which("node"), "нет node")
+class ClientColors(unittest.TestCase):
+ def test_panel_colors_js(self):
+ js = ca.CLIENT_COLORS_JS + (
+ "\nconsole.log(JSON.stringify([panelColors('#fdf6e3'), panelColors('#000000'),"
+ " panelColors('nope'), panelColors('#002b36')]))")
+ out = subprocess.run(["node", "-e", js], capture_output=True, text=True, check=True).stdout
+ self.assertEqual(json.loads(out), [
+ {"bg": "#fdf6e3", "fg": "#3b4a50", "dim": "#898f8b"},
+ {"bg": "#000000", "fg": "#aacace", "dim": "#66797c"},
+ None,
+ {"bg": "#002b36", "fg": "#aacace", "dim": "#668a91"},
+ ])
+
+
+if __name__ == "__main__":
+ unittest.main()