diff --git a/installer/HermesHubSetup.cs b/installer/HermesHubSetup.cs
index be08342..af141f2 100644
--- a/installer/HermesHubSetup.cs
+++ b/installer/HermesHubSetup.cs
@@ -18,7 +18,7 @@ namespace HermesHubSetup
// Подставляется сборщиком из фактического git-коммита. Раньше здесь
// жил зашитый "8cddc9f", то есть манифест сообщал неправду о том, из
// какого кода собран установщик.
- public const string BuildCommit = "fa7bbef";
+ public const string BuildCommit = "90aeceb";
public const string MIN_HERMES_VERSION = "0.20.0";
public const string MAX_TESTED_HERMES = "0.20.4";
diff --git a/launcher/HermesHub.exe b/launcher/HermesHub.exe
index 7cef02a..e966728 100644
Binary files a/launcher/HermesHub.exe and b/launcher/HermesHub.exe differ
diff --git a/launcher/HermesHubWeb.exe b/launcher/HermesHubWeb.exe
index 38e0dfd..57a0728 100644
Binary files a/launcher/HermesHubWeb.exe and b/launcher/HermesHubWeb.exe differ
diff --git a/src/antigravity_provider/router/web/static/index.html b/src/antigravity_provider/router/web/static/index.html
index 45cefe7..96ab776 100644
--- a/src/antigravity_provider/router/web/static/index.html
+++ b/src/antigravity_provider/router/web/static/index.html
@@ -457,7 +457,7 @@
-
+
Google отказывает по местоположению: «not currently available in your location». Выход через разрешённую страну снимает отказ. Пример: socks5://127.0.0.1:1080. Пусто — без прокси. Отдельному аккаунту можно задать свой адрес.
diff --git a/src/antigravity_provider/router/web/static/workspace.js b/src/antigravity_provider/router/web/static/workspace.js
index 02a99d6..299e64e 100644
--- a/src/antigravity_provider/router/web/static/workspace.js
+++ b/src/antigravity_provider/router/web/static/workspace.js
@@ -229,14 +229,32 @@ function arrangeSettingsPanels() {
['Общие настройки',['setting-default-role','setting-theme']],
['Управление квотами',['setting-account-check-interval','setting-quota-interval','setting-quota-threshold-percent','setting-quota-threshold-action']],
['Безопасность и API',['setting-server-host','setting-server-token-input','setting-email-masking-mode']],
+ ['Сеть и доступ',['setting-provider-proxy-url']],
];
for (const [title,ids] of groups) {
const card = document.createElement('section'); card.className='settings-card';
const heading=document.createElement('h2'); heading.className='settings-group-title'; heading.textContent=title; card.append(heading);
- for (const id of ids) card.append(document.getElementById(id).closest('.setting-row'));
+ for (const id of ids) {
+ const row = document.getElementById(id)?.closest('.setting-row');
+ if (row) card.append(row);
+ }
if (title === 'Управление квотами') card.append(document.getElementById('btn-save-hub-settings').closest('.settings-actions'));
view.insertBefore(card, first);
}
+
+ // Строки, которых нет ни в одной группе, раньше удалялись вместе с исходной
+ // карточкой: новая настройка просто пропадала с экрана, и найти её было
+ // нельзя. Так исчезло поле прокси. Теперь остаток не выбрасывается, а
+ // собирается отдельной карточкой — забыть настройку в списке всё ещё можно,
+ // но потерять её уже нет.
+ const leftovers = Array.from(first.querySelectorAll('.setting-row'));
+ if (leftovers.length) {
+ const card = document.createElement('section'); card.className='settings-card';
+ const heading=document.createElement('h2'); heading.className='settings-group-title';
+ heading.textContent='Прочие настройки'; card.append(heading);
+ for (const row of leftovers) card.append(row);
+ view.insertBefore(card, first);
+ }
first.remove();
}
document.addEventListener('DOMContentLoaded', arrangeSettingsPanels);
diff --git a/tests/test_settings_screen_keeps_every_row.py b/tests/test_settings_screen_keeps_every_row.py
new file mode 100644
index 0000000..3161611
--- /dev/null
+++ b/tests/test_settings_screen_keeps_every_row.py
@@ -0,0 +1,61 @@
+"""Экран настроек не должен молча терять поля.
+
+Владелец не нашёл настройку прокси. Она была в разметке, но исчезала при
+открытии экрана: arrangeSettingsPanels пересобирает настройки по жёсткому
+списку идентификаторов, а исходную карточку удаляет целиком — вместе со всем,
+чего в списке нет.
+
+Поле существовало в файле, но его не было на экране, и никакая проверка этого
+не ловила.
+"""
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+import pytest
+
+STATIC = Path(__file__).resolve().parent.parent / "src" / "antigravity_provider" / "router" / "web" / "static"
+
+
+@pytest.fixture(scope="module")
+def markup() -> str:
+ return (STATIC / "index.html").read_text(encoding="utf-8")
+
+
+@pytest.fixture(scope="module")
+def workspace() -> str:
+ return (STATIC / "workspace.js").read_text(encoding="utf-8")
+
+
+def _grouped_ids(workspace: str) -> list[str]:
+ block = workspace.split("const groups = [")[1].split("];")[0]
+ return re.findall(r"'(setting-[a-z0-9-]+)'", block)
+
+
+def test_every_grouped_id_exists_in_the_markup(markup, workspace):
+ for element_id in _grouped_ids(workspace):
+ assert f'id="{element_id}"' in markup, f"{element_id} перечислен в группах, но его нет в разметке"
+
+
+def test_proxy_setting_is_reachable(markup, workspace):
+ assert 'id="setting-provider-proxy-url"' in markup
+ assert "setting-provider-proxy-url" in _grouped_ids(workspace), (
+ "негруппированная строка удаляется вместе с исходной карточкой"
+ )
+
+
+def test_unlisted_rows_are_kept_not_deleted(workspace):
+ """Забыть настройку в списке можно, потерять её с экрана — нет."""
+ body = workspace.split("function arrangeSettingsPanels")[1].split("function ")[0]
+
+ assert "leftovers" in body, "остаток строк должен собираться, а не выбрасываться"
+ assert body.index("leftovers") < body.index("first.remove()"), (
+ "остаток надо забрать до удаления исходной карточки"
+ )
+
+
+def test_missing_element_does_not_break_the_screen(workspace):
+ """Опечатка в списке не должна ронять сборку экрана целиком."""
+ body = workspace.split("function arrangeSettingsPanels")[1].split("function ")[0]
+ assert "?.closest" in body, "обращение к возможному null должно быть защищено"