fix(orchestrator): keep polling while text stays selected
The hub page skipped every 10 s state poll while any text was selected, so operator rows that operator.py run had closed stayed on screen until the selection ended (2026-11-12: no /api/state from 09:29 to 09:54 while the CLI closed 19 rows). A selection now holds the poll back for at most 20 s, and returning to the window reloads at once. 🎯 Fix: 90 % 💥 Break: 3 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 87 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd8fc08603
commit
07d28a62c6
3 files changed
+71
-1
No files matched your search
@@ -28,6 +28,7 @@
|
||||
document.addEventListener("pointerdown", unlock);
|
||||
document.addEventListener("keydown", keyboard);
|
||||
document.addEventListener("visibilitychange", refresh);
|
||||
window.addEventListener("focus", refresh);
|
||||
window.addEventListener("hashchange", apply);
|
||||
window.addEventListener("resize", measure);
|
||||
window.addEventListener("resize", redrawSky);
|
||||
|
||||
@@ -30,8 +30,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
const SELECTION_HOLD = 20000;
|
||||
let selectedSince = 0;
|
||||
|
||||
function refresh() {
|
||||
if (!ui.busy && !ui.playing && !document.hidden && String(window.getSelection()).length === 0) {
|
||||
const selected = String(window.getSelection()).length > 0;
|
||||
selectedSince = selected ? selectedSince || Date.now() : 0;
|
||||
if (!ui.busy && !ui.playing && !document.hidden && (!selected || Date.now() - selectedSince >= SELECTION_HOLD)) {
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import json
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
TEMPLATES = Path(__file__).resolve().parents[1] / "orchestrator" / "templates"
|
||||
LOAD = TEMPLATES / "page-load.js"
|
||||
BOOT = TEMPLATES / "page-boot.js"
|
||||
PROBE = """
|
||||
const fs = require("fs");
|
||||
const vm = require("vm");
|
||||
const code = fs.readFileSync(process.argv[1], "utf8");
|
||||
const steps = JSON.parse(process.argv[2]);
|
||||
const loads = [];
|
||||
const page = {now: 0, selection: ""};
|
||||
const context = vm.createContext({
|
||||
ui: {busy: false, playing: false},
|
||||
document: {hidden: false},
|
||||
window: {getSelection: () => page.selection},
|
||||
Date: {now: () => page.now * 1000},
|
||||
});
|
||||
vm.runInContext(code, context);
|
||||
context.load = () => loads.push(page.now);
|
||||
for (const step of steps) {
|
||||
page.now = step.at;
|
||||
page.selection = step.selection || "";
|
||||
context.ui.busy = Boolean(step.busy);
|
||||
context.document.hidden = Boolean(step.hidden);
|
||||
vm.runInContext("refresh()", context);
|
||||
}
|
||||
console.log(JSON.stringify(loads));
|
||||
"""
|
||||
|
||||
|
||||
def loads(steps: list[dict]) -> list[int]:
|
||||
done = subprocess.run(["node", "-e", PROBE, str(LOAD), json.dumps(steps)], capture_output=True, text=True, timeout=60, check=True)
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
class TestHubRefresh(unittest.TestCase):
|
||||
def test_a_selection_holds_the_poll_back_for_twenty_seconds_only(self):
|
||||
steps = [
|
||||
{"at": 0},
|
||||
{"at": 10, "selection": "git push"},
|
||||
{"at": 20, "selection": "git push"},
|
||||
{"at": 30, "selection": "git push"},
|
||||
{"at": 40, "selection": "git push"},
|
||||
{"at": 50},
|
||||
{"at": 60, "selection": "other"},
|
||||
{"at": 70, "selection": "other"},
|
||||
]
|
||||
self.assertEqual(loads(steps), [0, 30, 40, 50])
|
||||
|
||||
def test_busy_and_hidden_pages_still_skip_the_poll(self):
|
||||
self.assertEqual(loads([{"at": 0, "busy": True}, {"at": 10, "hidden": True}, {"at": 20}]), [20])
|
||||
|
||||
def test_returning_to_the_window_reloads_at_once(self):
|
||||
boot = BOOT.read_text(encoding="utf-8")
|
||||
self.assertIn('window.addEventListener("focus", refresh);', boot)
|
||||
self.assertIn('document.addEventListener("visibilitychange", refresh);', boot)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user