Release 3.8.2.6: perceived-speed UX — search, tray hide, core warm, SSE.
ci / test (macos-latest) (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run

Server search/hotkeys; Windows close-to-tray; background EnsureCore; SSE status with rare poll fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-08-01 16:32:19 +03:00
co-authored by Cursor
parent 2ad376b49e
commit 5d7c96f7fc
23 changed files with 409 additions and 33 deletions
+71 -3
View File
@@ -51,6 +51,7 @@
const bestBtn = $("bestBtn");
const autoBest = $("autoBest");
const serverList = $("serverList");
const serverSearch = $("serverSearch");
const updCheckBtn = $("updCheckBtn");
const updateBanner = $("updateBanner");
const updateBtn = $("updateBtn");
@@ -251,17 +252,35 @@
return list;
}
function filteredProfiles() {
const q = (serverSearch && serverSearch.value || "").trim().toLowerCase();
const list = orderedProfiles();
if (!q) return list;
return list.filter((p) => {
const hay = [p.name, p.host, p.protocol].map((x) => String(x || "").toLowerCase()).join(" ");
return hay.includes(q);
});
}
function renderServerList(activeName) {
const active = activeName || profile.value;
serverList.innerHTML = "";
const list = orderedProfiles();
if (!list.length) {
const all = orderedProfiles();
const list = filteredProfiles();
if (!all.length) {
const empty = document.createElement("div");
empty.className = "server-empty";
empty.textContent = "Нет серверов — добавьте профиль или подписку";
serverList.appendChild(empty);
return;
}
if (!list.length) {
const empty = document.createElement("div");
empty.className = "server-empty";
empty.textContent = "Ничего не найдено";
serverList.appendChild(empty);
return;
}
list.forEach((p) => {
const row = document.createElement("button");
row.type = "button";
@@ -707,11 +726,60 @@
setMeta(String(e), "err");
}
})();
if (serverSearch) {
serverSearch.addEventListener("input", () => renderServerList(profile.value));
}
function typingInField(el) {
if (!el) return false;
const tag = (el.tagName || "").toLowerCase();
return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable;
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("open")) {
modal.classList.remove("open");
e.preventDefault();
return;
}
if (typingInField(document.activeElement)) return;
if ((e.key === "/" || (e.key === "f" && (e.ctrlKey || e.metaKey))) && serverSearch) {
e.preventDefault();
serverSearch.focus();
serverSearch.select();
return;
}
if ((e.key === "Enter" || e.key === " ") && !busy) {
e.preventDefault();
btn.click();
}
});
// Safety-net poll; primary updates come from SSE /api/events when available.
setInterval(() => {
if (document.hidden || busy || modal.classList.contains("open")) return;
refresh().catch(() => {});
}, 4000);
}, 12000);
document.addEventListener("visibilitychange", () => {
if (!document.hidden && !busy) refresh({ force: true }).catch(() => {});
});
(function startEventStream() {
if (!window.__navisHttp || typeof EventSource === "undefined") return;
const tok = window.__NAVIS_TOKEN__ || "";
const url = "/api/events" + (tok ? ("?t=" + encodeURIComponent(tok)) : "");
let es;
const connectES = () => {
try { if (es) es.close(); } catch (_) {}
es = new EventSource(url);
es.onmessage = () => {
if (!busy) refresh({ force: true }).catch(() => {});
};
es.onerror = () => {
try { es.close(); } catch (_) {}
setTimeout(connectES, 2500);
};
};
connectES();
})();