Release 3.8.2.6: perceived-speed UX — search, tray hide, core warm, SSE.
ci / test (macos-latest) (push) Canceled after 0s
ci / test (ubuntu-latest) (push) Canceled after 0s
ci / test (windows-latest) (push) Canceled after 0s

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
+22
View File
@@ -548,6 +548,28 @@
margin-bottom: 8px;
}
.server-toolbar .section-title { margin: 0; }
.server-search-wrap {
margin: 0 0 8px;
}
.server-search-wrap input {
width: 100%;
box-sizing: border-box;
border: 1px solid rgba(8, 90, 68, 0.18);
background: var(--field-bg, rgba(255,255,255,.55));
color: inherit;
border-radius: 12px;
padding: 9px 12px;
font: inherit;
font-size: .88rem;
outline: none;
}
.server-search-wrap input:focus {
border-color: rgba(37, 88, 75, 0.45);
}
[data-theme="dark"] .server-search-wrap input {
background: rgba(0,0,0,.22);
border-color: rgba(180, 220, 200, 0.14);
}
.server-actions {
display: flex;
gap: 6px;
+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();
})();
+3
View File
@@ -85,6 +85,9 @@
<button class="icon-btn danger" id="delBtn" type="button" title="Удалить" style="width:34px;height:34px;font-size:1rem">×</button>
</div>
</div>
<div class="server-search-wrap">
<input id="serverSearch" type="search" placeholder="Поиск по имени, хосту, протоколу…" spellcheck="false" autocomplete="off" />
</div>
<label class="auto-best">
<input id="autoBest" type="checkbox" />
Авто: пинг + подключение к лучшему