fix: harden UI actions and daemon calls for reliable control (v1.3.2)

Retry and serialize pineapd/hak5 calls, queue virtual-pager keys, and grey out buttons until the pager finishes. Deploy now installs python3-light after factory firmware. Bump version to 1.3.2.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 15:49:10 -05:00
co-authored by Cursor
parent cd26553d21
commit 7d48b7ad06
25 changed files with 3625 additions and 782 deletions
@@ -5,6 +5,9 @@ const Term = (() => {
let fitAddon = null;
let ws = null;
let panel = null;
let wantOpen = false;
let retryTimer = null;
let retryMs = 400;
function ensure() {
if (term) return;
@@ -14,7 +17,20 @@ const Term = (() => {
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
try { fitAddon.fit(); } catch (e) {}
term.onData((d) => { if (ws && ws.readyState === WebSocket.OPEN) ws.send(d); });
term.onData((d) => {
if (ws && ws.readyState === WebSocket.OPEN) {
try { ws.send(d); } catch (e) {}
}
});
}
function scheduleReconnect() {
if (!wantOpen) return;
clearTimeout(retryTimer);
retryTimer = setTimeout(() => {
if (wantOpen) connect();
}, retryMs);
retryMs = Math.min(5000, Math.max(400, retryMs * 2));
}
function toggle() {
@@ -23,6 +39,8 @@ const Term = (() => {
panel.classList.remove('hidden');
document.getElementById('terminal-btn').classList.add('active');
try { fitAddon.fit(); } catch (e) {}
wantOpen = true;
retryMs = 400;
connect();
} else {
panel.classList.add('hidden');
@@ -32,25 +50,39 @@ const Term = (() => {
}
function connect() {
if (ws) return;
if (!wantOpen) return;
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
if (term) term.reset();
let sock;
try {
sock = new WebSocket(App.terminalWs);
} catch (e) {
term.writeln('\r\n[cannot reach daemon terminal: ' + e.message + ']');
scheduleReconnect();
return;
}
ws = sock;
sock.onopen = () => { retryMs = 400; };
sock.onmessage = (ev) => {
if (typeof ev.data === 'string') term.write(ev.data);
else ev.data.text().then((t) => term.write(t));
};
sock.onclose = () => { if (ws === sock) ws = null; if (term) term.writeln('\r\n[connection closed]'); };
sock.onclose = () => {
if (ws === sock) ws = null;
if (term && wantOpen) {
term.writeln('\r\n[connection closed — reconnecting]');
scheduleReconnect();
} else if (term) {
term.writeln('\r\n[connection closed]');
}
};
sock.onerror = () => { try { sock.close(); } catch (e) {} };
}
function disconnect() {
wantOpen = false;
clearTimeout(retryTimer);
retryTimer = null;
if (ws) { try { ws.close(); } catch (e) {} ws = null; }
}