Some checks failed
Windows project source guards / test (push) Failing after 32s
Инцидент 28.09.2026: сервер сменился Tiraru → Titiru (мёртвый Hysteria), а в логе был только [core-switch] без причины — явное действие (кнопка «Следующий», трей, таблица) было неотличимо от сбоя программы. - SelectionSource (application/selection_source.py): кто сменил сервер и ручной ли это выбор. set_selected_node/switch_next_node принимают обязательный source= вместо reset_auto_switch; ручной источник сбрасывает учёт авто-переключения, автоматика (auto/smart/rotation) — нет. - Каждая смена пишет одну строку [select] source=<код> (<описание>): «было» node_ref=… → «стало» node_ref=… node_ref совпадает с [runtime-map] и строками ядер. Покрыты и неявные смены: импорт, удаление, скрытие, подписка, восстановление Hysteria. - UI передаёт жест: двойной клик/Enter/меню таблицы, трей, главная; сигнал NodesPage.selected_node_changed → connect_node_requested(id, source). - Кнопка «Следующий» на главной не берёт фокус клавиатуры: пробел/Enter «мимо» больше не меняют сервер. - tests/test_selection_source.py: AST-инвариант — каждый вызов команды смены передаёт source=, а .selected_node_id присваивают только функции из белого списка, которые пишут [select]. switch_prev_node (мёртвый) удалён.
44 lines
2.7 KiB
Python
44 lines
2.7 KiB
Python
"""Источник смены выбранного сервера.
|
||
|
||
Каждая смена ``state.selected_node_id`` называет свой источник и пишет в лог
|
||
одну строку ``[select] source=…``. Без этого явное действие пользователя
|
||
(кнопка «Следующий», трей, таблица) в диагностике неотличимо от сбоя
|
||
программы: инцидент 28.09.2026 — в логе был только ``[core-switch]`` без
|
||
причины.
|
||
|
||
Модуль-лист без импортов проекта: его импортируют и application-, и UI-слой.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from enum import Enum
|
||
|
||
|
||
class SelectionSource(Enum):
|
||
"""Кто сменил выбранный сервер.
|
||
|
||
``manual`` — выбор сделал пользователь: он сбрасывает учёт анти-дребезга
|
||
авто-переключения и фиксирует сервер (manual hold). Автоматические
|
||
источники этот учёт не трогают.
|
||
"""
|
||
|
||
# Явные действия пользователя.
|
||
DASHBOARD_NEXT = ("dashboard-next", "кнопка «Следующий» на главной", True)
|
||
TRAY_NEXT = ("tray-next", "трей → «Следующий сервер»", True)
|
||
NODES_DOUBLE_CLICK = ("nodes-double-click", "двойной клик в таблице серверов", True)
|
||
NODES_ENTER = ("nodes-enter", "Enter в таблице серверов", True)
|
||
NODES_MENU = ("nodes-menu", "меню таблицы серверов → «Подключить»", True)
|
||
NODES_IMPORTED = ("nodes-imported", "импорт серверов: выбран первый новый", True)
|
||
# Автоматика.
|
||
AUTO_SWITCH = ("auto-switch", "авто-переключение: сервер недоступен", False)
|
||
SMART_SWITCH = ("smart-switch", "умная проверка: сервер медленный", False)
|
||
ROTATION = ("rotation", "ротация серверов", False)
|
||
HYSTERIA_RECOVERY = ("hysteria-recovery", "отказ Hysteria: резервный сервер", False)
|
||
# Выбранный сервер исчез из списка — выбран первый оставшийся.
|
||
NODE_REMOVED = ("node-removed", "выбранный сервер удалён", False)
|
||
SUBSCRIPTION = ("subscription", "подписка: выбранного сервера больше нет", False)
|
||
|
||
def __init__(self, code: str, label: str, manual: bool) -> None:
|
||
self.code = code
|
||
self.label = label
|
||
self.manual = manual
|