From e431e39915a543dacefbf200d5b796a48f5e3cae Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Wed, 2 Sep 2026 00:30:47 +0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B0=20=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8=20?= =?UTF-8?q?=D0=B8=D1=81=D1=87=D0=B5=D0=B7=D0=B0=D0=BB=D0=B0=20=D1=81=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Владелец не нашёл настройку. Она была в разметке, но её не было на экране. arrangeSettingsPanels пересобирает настройки по жёсткому списку идентификаторов, переносит перечисленные строки в новые карточки, а исходную удаляет целиком — вместе со всем, чего в списке нет. Новое поле попало под удаление и просто перестало существовать. Добавлена группа «Сеть и доступ» с полем прокси. Название уточнено до «Прокси / VPN для провайдеров»: владелец называет это впном, и искать он будет по этому слову. Устройство, которое так теряет настройки, тоже исправлено: строки, не попавшие ни в одну группу, собираются в карточку «Прочие настройки», а не выбрасываются. Забыть настройку в списке всё ещё можно, потерять её с экрана — уже нет. Обращение к отсутствующему элементу защищено: опечатка в списке больше не роняет сборку экрана целиком. Четыре теста закрывают это: каждый перечисленный идентификатор существует в разметке, поле прокси сгруппировано, остаток забирается до удаления карточки. 704 passed, 2 skipped; ruff чисто; релизный гейт пройден. Co-Authored-By: Claude Opus 5 --- installer/HermesHubSetup.cs | 2 +- launcher/HermesHub.exe | Bin 6144 -> 6144 bytes launcher/HermesHubWeb.exe | Bin 20992 -> 20992 bytes .../router/web/static/index.html | 2 +- .../router/web/static/workspace.js | 20 +++++- tests/test_settings_screen_keeps_every_row.py | 61 ++++++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 tests/test_settings_screen_keeps_every_row.py 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 7cef02a0f0679ff718b83fc73bb4e344f02e0769..e966728ce18e7edfd2bc9a5bab7068b83ae36771 100644 GIT binary patch delta 35 tcmV+;0Nnq8Fn}gnqyd1W0g#9Sv-7cv`XD4YLo_o&GebfxH8MCwEi^$nMlCosF-9#gF*8L( lL^U-
- +
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 должно быть защищено"