github Leadaxe/singbox-launcher v1.5.6
release v1.5.6

2 hours ago

Release v1.5.6

Downloads

macOS (Universal) - Supports both Apple Silicon and Intel

Option 1: Installation Script (Recommended)

Install with a single command (version v1.5.6):

curl -fsSL https://raw.githubusercontent.com/Leadaxe/singbox-launcher/develop/scripts/install-macos.sh | bash -s -- v1.5.6

The script will:

  • Download the release archive
  • Extract and install to /Applications/
  • Fix macOS quarantine attributes and permissions
  • Launch the application automatically

Option 2: Manual Installation

  1. Download: singbox-launcher-v1.5.6-macos.zip
  2. Extract the ZIP file
  3. Remove quarantine attribute (required):
    xattr -cr "singbox-launcher.app" && chmod +x "singbox-launcher.app/Contents/MacOS/singbox-launcher"
  4. Double-click singbox-launcher.app to run
    • If macOS blocks the app, go to System Settings → Privacy & Security and click "Open Anyway"
    • Alternatively, right-click the app and select "Open" (first time only)

Windows (amd64)

  1. Download: singbox-launcher-v1.5.6-win64.zip
    • or singbox-launcher-v1.5.6-win64-full.zipfull bundle: sing-box.exe, wintun.dll, the config template and Mesa3D (mesa3d/, used only when no hardware OpenGL — RDP, VMs) are already inside, nothing is downloaded on first launch
  2. Extract the ZIP file to a folder, for example: C:\Program Files\singbox-launcher\
  3. Run singbox-launcher.exe from that folder
    • You may need administrator rights to install to Program Files
    • The regular archive downloads sing-box and wintun.dll on first launch

Windows 7 (x86, legacy)

  1. Download: singbox-launcher-v1.5.6-win7-32.zip
  2. Extract the ZIP file to a folder and run singbox-launcher-win7-32.exe
    • For Windows 7 / 32-bit or legacy compatibility only

Linux Support

⚠️ Linux build temporarily unavailable - мы ищем тестировщика для ручного тестирования перед включением автоматической сборки.

Checksums

See checksums.txt for SHA256 checksums of all files.

Release notes — v1.5.6

EN

Highlights

  • The launcher can be a Tailscale node. Add server → Tailscale asks for an auth key and, optionally, a control URL, hostname, exit node and the ephemeral / accept-routes switches — and adds not just the node but everything it needs to be useful: a MagicDNS server for *.ts.net and a route rule for 100.64.0.0/10, carried with the node as its own config fragments. Tailnet names resolve and tailnet hosts are reachable right after Rebuild, with no hand-written JSON on three separate tabs. A tailscale node pasted as sing-box JSON is imported the same way instead of being rejected as an unknown type, and the DNS tab now edits tailscale servers with a form. Each node gets its own state directory (bin/tailscale/<tag>), so a second tailnet node does not overwrite the first one's identity. The key is one-off: it is consumed at first login, and from then on the identity lives in that directory — delete it and the tailnet registers a new device. A tailnet node is offered in a Direction only when it has an exit node: without one it opens the tailnet, not the internet.

  • A node can now carry the config fragments it needs. Some servers are useless on their own: a WireGuard endpoint with subnets behind the peer needs a DNS server bound to it, a DNS rule for its domains and a route rule for its subnets. Paste a whole sing-box config with exactly one node into Add and the launcher keeps that bundle with the node instead of dropping dns/route as it used to; the node's JSON tab shows and edits it as a document, with @self (and @{self} inside a longer string) standing for the node's own tag so the bundle survives a rename. What the node carries are ordinary launcher entries: each route rule gets its own draggable row in the rules list, named after the rule and the node, with its own toggle and its own place in the order — bodies are edited on the node, so the rows have no gear or delete button. Its DNS server and DNS rule show up read-only at the end of the DNS tab. Disable the node and none of it reaches the config.

  • AmneziaWG obfuscation is now editable on an existing node. Open a WireGuard node in Sources and the Settings tab carries an obfuscation block right below Detour: the masquerade domain and browser (id / ib) plus the junk counters jc / jmin / jmax. Until now those fields only ever arrived with the import — a server that started demanding obfuscation after the node was created meant going back to the provider for a fresh link. Edits are written into the node as you type. The block is hidden when a detour is set, because a node dialled through another outbound raises no transport of its own: choosing a detour clears the fields from the node, and a node that arrives carrying both shows a warning instead of the form. The masquerade protocol is fixed to quic; s1-s4 and h1-h4 are never written, since any value there other than the WireGuard default stops the server from recognising the handshake.

  • AmneziaWG 3.0/3.1 servers import as they are. Amnezia now hands out AWG 3.x profiles, and until now the launcher quietly threw away everything the new protocol adds: header protection, content padding, random trailers, cookie suppression and the ranged timings. The node looked fully configured and the handshake could never succeed. A vpn:// profile, a pasted .conf and a wireguard:// link now all carry those fields through to the config, along with two details the export keeps outside the [Interface] block: the MTU (read from last_config.mtu and clamped to the AmneziaWG ceiling of 1280 like any other AmneziaWG node) and the real DNS addresses behind Amnezia's $PRIMARY_DNS / $SECONDARY_DNS placeholders. The server list labels such a node wireguard·awg3 or wireguard·awg3.1. This needs core 1.14.0-lx.32; on an older core the node is dropped with a build-report line that names the version you have and the one you need, instead of the whole config being rejected.

Fixes

  • Mesa3D installed itself on machines with a working GPU and killed the launcher (#125). In the win64-full archive the Mesa DLLs ship next to the exe, and the gate copied them in without ever asking: any failure of the OpenGL probe — including "the driver did not answer within 10 seconds" — was read as "this machine has no hardware OpenGL". A probe that times out on a home PC is a temporary driver hiccup, not a verdict, and the copy made things worse rather than better: Mesa was never pinned to a driver, so on a machine with a GPU it picked d3d12, the very stack that had just hung, and the process died a second after the window appeared. Nothing was left to debug with — no Go panic, so crash.log stayed empty, and the window build has no stderr at all, so whatever the driver printed vanished. There was also no way back: the switch to software rendering was invisible in the UI and irreversible without knowing which file to delete. Now the gate distinguishes a timeout from a refusal and offers Retry / Ignore / Abort; Mesa is never installed without an explicit Yes in a dialog; GALLIUM_DRIVER=llvmpipe is pinned before Mesa loads (unless you set that variable yourself) and the installed DLLs are verified by a second probe that loads them by full path — if they do not come up as llvmpipe, the install is rolled back on the spot. What renders the window is now recorded in bin/gl-state.json, together with whether the previous start ever reached a frame: a start that died under Mesa while hardware OpenGL works is offered a switch back on the next launch, every Mesa start says so in the log, the Diagnostics tab has a button to toggle the renderer either way (the DLLs are renamed to .off, never deleted), and native output from Mesa, drivers and GLFW now lands in logs/native-stderr.log instead of nowhere. With -tray no dialog is shown and no file is touched. Switching the renderer now restarts the launcher for you: OPENGL32.dll sits in the exe's import table, so Windows maps it before any of the launcher's own code runs and the renderer simply cannot change inside a live process — the first release candidate answered "yes, disable Mesa", carried on with Mesa still mapped and crashed a second time. For the same reason the hardware probe now moves the local opengl32.dll aside while it runs, and discards any "hardware" answer whose GL vendor turns out to be Mesa; previously it reported Mesa's D3D12 (NVIDIA GeForce GT 440) as if it were the GPU itself.

  • macOS: the file picker greys out every file (#120). On macOS 26 the native open panel no longer understands a bare extension as a type filter, so Import… on the Settings tab and Add from file showed the backup and config files disabled and there was nothing to select. The launcher now resolves each extension to a system type identifier before opening the panel; extensions without a registered type (.vpn, .conf) get a dynamic one, which the panel accepts. If nothing resolves, the panel opens without a filter rather than empty.

  • Backup import lost the routing rules and the default direction (#111). The import report counted the file's rules as applied, but only preset rules reached the Rules tab: user and rule-set rules were written to the canonical list while the wizard kept reading its previous view of them. route.final from the file went into a field the wizard never reads, so the Default direction stayed as it was, and the launcher's own export never wrote route.final at all. Both channels are now the ones the wizard actually uses.

  • A drag-and-drop reorder could leave the whole window deaf to clicks. The insertion line drawn while dragging a row (Sources, Rules, DNS, Directions, folder contents) lives in the canvas overlay stack, and it was removed as "whatever is on top". If anything else had landed on top mid-drag, the wrong overlay was removed and the line's wrapper stayed behind forever — and Fyne routes every click to the top overlay only, so the window kept rendering but ignored all input while the process was perfectly alive (two freezes on 7–8 September, both needing a restart). The wrapper is now removed by identity, anything above it is put back, and every drag end and every list rebuild clears it.

  • REALITY nodes stopped working against updated servers, silently. Xray v26.9.8 tightened its REALITY handshake: the server now wants a post-quantum key share in the ClientHello, and among the uTLS fingerprints only the Chrome ones send it. A node whose link said fp=firefox — or fp=safari, ios, android, edge, 360, qq — was answered by the decoy site instead of the proxy, so the connection looked established and simply carried nothing: no error in the log, nothing for sing-box check to catch. The default random picked a dead fingerprint four times out of five, which is why the same node worked one evening and not the next. The launcher now uses a Chrome fingerprint whenever it emits a REALITY block, leaving the link's own value visible on the node and flagging the substitution in the build report. Nodes whose servers are still on an older Xray are unaffected — a Chrome ClientHello is accepted by both.

  • An SRS rule keeps every rule-set it was given. A rule built from several SRS URLs used to keep only the first one: the state record held a single URL, so after the Configurator was reopened the other sets were gone, and the build emitted just the first set even in the session that created the rule (reported against 1.5.5). The record now carries the full list: the rule restores all of its sets, the build emits one local rule-set per URL and a single route rule that references them all, and the orphan cleanup keeps every downloaded file. Backups carry the whole list too, as refs next to the existing ref (a draft contract field, so an app that does not know it still gets the first set, as before).

Technical / Internal

  • A tailscale node on a core without the tag is dropped, not fatal. The endpoint exists only in cores built with with_tailscale (the fork from 1.14.0-lx.31), and a single such node makes sing-box check reject the whole config — every other server included. The launcher probes the core's build tags once per build and drops those nodes with a warning that names the cause in the build report, exactly as it already does for naive. An unreadable sing-box version still counts as "supported": nodes are never dropped on guesswork.

  • The AmneziaWG 3.x gate is a version gate, not a tag gate. with_awg has been in the core for years, but header_protection_key and its neighbours only landed in 1.14.0-lx.32, and an older core rejects the whole config as invalid JSON rather than the one node it cannot read. The launcher therefore reads both the build tags and the version from sing-box version once per build: no with_awg → no AmneziaWG at all; base version above 1.14.0 → supported; exactly 1.14.0 → the -lx.N release number decides. Unreadable output still counts as "supported" — nodes are never dropped on guesswork. The MTU rule is unchanged: an AWG 3.x node is clamped to 1280 like any AmneziaWG endpoint — on the reference server the export's own 1376 carried no data, 1280 did.

  • A pasted wg-quick config no longer loses its masquerade silently. The INI reader carried the junk numbers and the explicit i1-i5 tags but skipped ip / id / ib, so a config with masquerade looked fully configured while its first decoy packet went out undisguised. The same sugar now also counts as an AmneziaWG marker on its own, which clamps such a node's MTU to the AmneziaWG ceiling — without that, the endpoint fails the silent way: the handshake completes and no data flows.

  • GET /debug/goroutines — a goroutine dump without killing the launcher. When the Fyne UI freezes, the Go side usually keeps running: the debug API answers and the hourly heartbeat still logs. Until now there was no way to see what the main loop was waiting on: the launcher's stderr goes to /dev/null when started from Finder, so a SIGQUIT dump is lost, and an lldb attach kills the process outright. The new endpoint returns the same text Go prints on SIGQUIT — every goroutine's stack as text/plain, X-Goroutines header with the count — while the process keeps running. goroutine 1 is the Fyne/GLFW main loop. Take the dump before restarting a frozen launcher.

  • GET /debug/ui and POST /debug/ui/overlays/clear — see and clear the overlay stack. The snapshot lists each window's canvas size, focused widget and overlay stack (type, position, size, children), which is exactly where a stale overlay shows up; the clear call drops every overlay and gives the window back without a restart. 504 ui loop unresponsive means the Fyne main loop itself is blocked, which is a different bug. Capability ui in the manifest.

RU

Основное

  • Лаунчер умеет быть узлом Tailscale. Add server → Tailscale спрашивает auth key и, по желанию, control URL, hostname, exit node и галки ephemeral / accept routes — и заводит не только узел, но и всё, без чего он бесполезен: MagicDNS-сервер на *.ts.net и правило маршрута на 100.64.0.0/10, которые едут вместе с узлом его собственными фрагментами конфига. Имена tailnet резолвятся, а хосты доступны сразу после Rebuild, без ручного JSON на трёх вкладках. Узел tailscale, вставленный sing-box-конфигом, теперь импортируется так же, а не отвергается как неизвестный тип; DNS-сервер типа tailscale вкладка DNS правит формой. У каждого узла свой каталог состояния (bin/tailscale/<тег>) — второй узел tailnet не затирает идентичность первого. Ключ одноразовый: расходуется при первом входе, дальше идентичность живёт в этом каталоге, и его удаление регистрирует в tailnet новое устройство. В Направление узел tailnet предлагается только с указанным exit node: без него он открывает саму tailnet, а не выход в интернет.

  • Узел может нести с собой фрагменты конфига, без которых он не работает. Некоторые серверы бесполезны в одиночку: WireGuard-endpoint с подсетями за пиром требует привязанного к нему DNS-сервера, DNS-правила на его домены и правила маршрута на его подсети. Вставьте в Add целый sing-box-конфиг ровно с одним узлом — лаунчер сохранит эту связку вместе с узлом, а не выбросит dns/route, как раньше; вкладка JSON узла показывает и правит её документом, где @self@{self} внутри строки) означает собственный тег узла, так что связка переживает переименование. То, что несёт узел, — обычные записи лаунчера: каждое правило маршрута встаёт в списке правил своей перетаскиваемой строкой с именем правила и узла, со своим тумблером и своим местом в порядке; тела правятся у узла, поэтому у строк нет ни шестерёнки, ни удаления. DNS-сервер и DNS-правило видны только для чтения в конце вкладки DNS. Узел выключен — в конфиг не попадает ничего из этого.

  • Обфускация AmneziaWG правится у уже заведённого узла. Откройте WireGuard-узел в Sources — на вкладке Settings сразу под Detour появился блок обфускации: домен и браузер маскировки (id / ib) плюс junk-числа jc / jmin / jmax. Раньше эти поля приезжали только с импортом, и сервер, начавший требовать обфускацию после заведения узла, отправлял пользователя к провайдеру за новой ссылкой. Правки пишутся в узел по мере ввода. При выбранном detour блок скрыт: узел, ходящий через чужой outbound, своего транспорта не поднимает — назначенный detour снимает поля с узла, а узел, приехавший сразу с обоими, показывает предупреждение вместо формы. Протокол маскировки закреплён как quic; s1s4 и h1h4 не пишутся вовсе, потому что любое значение там, кроме дефолтного для WireGuard, лишает сервер возможности узнать рукопожатие.

  • Серверы AmneziaWG 3.0/3.1 импортируются как есть. Amnezia раздаёт профили AWG 3.x, а лаунчер до сих пор молча выбрасывал всё, что добавляет новый протокол: защиту заголовка, добивку содержимого, случайные хвосты, подавление cookie и диапазонные тайминги. Узел выглядел полностью настроенным, а рукопожатие не могло пройти никогда. Профиль vpn://, вставленный .conf и ссылка wireguard:// теперь доносят эти поля до конфига — вместе с двумя вещами, которые экспорт держит вне блока [Interface]: MTU (читается из last_config.mtu и ужимается до потолка AmneziaWG 1280, как у любого узла AmneziaWG) и настоящие адреса DNS за плейсхолдерами Amnezia $PRIMARY_DNS / $SECONDARY_DNS. В списке серверов такой узел подписан wireguard·awg3 или wireguard·awg3.1. Нужно ядро 1.14.0-lx.32: на более старом узел снимается со строкой в отчёте сборки, называющей и вашу версию, и требуемую, — вместо того чтобы ядро отвергло весь конфиг целиком.

Исправления

  • Mesa3D ставилась сама на машины с исправной видеокартой и убивала лаунчер (#125). В архиве win64-full DLL Mesa лежат рядом с exe, и гейт копировал их вообще без вопроса: любая неудача пробы OpenGL — включая «драйвер не ответил за 10 секунд» — читалась как «аппаратного OpenGL на этой машине нет». На домашнем ПК зависшая проба означает временную икоту драйвера, а не приговор, и копия делала только хуже: драйвер Mesa никто не пинил, поэтому на машине с видеокартой она уходила в d3d12 — тот самый стек, который только что завис, — и процесс умирал через секунду после появления окна. Отлаживать было нечем: паники Go не было, значит crash.log оставался пустым, а у оконной сборки нет stderr, и всё, что печатал драйвер, пропадало. Пути назад тоже не было: переход на программный рендеринг никак не отражался в UI и не откатывался, если не знать, какой файл удалить. Теперь гейт отличает таймаут от отказа и предлагает Retry / Ignore / Abort; Mesa не ставится без явного «Yes» в диалоге; перед загрузкой Mesa пинится GALLIUM_DRIVER=llvmpipe (если вы не задали переменную сами), а поставленные DLL проверяются второй пробой, которая грузит их по полному пути — не поднялись как llvmpipe, установка тут же откатывается. Чем рисуется окно, теперь записано в bin/gl-state.json вместе с тем, дошёл ли прошлый старт до кадра: старту, умершему под Mesa при живом железе, на следующем запуске предложат вернуться обратно, каждый старт через Mesa пишет об этом в лог, на вкладке Диагностика появилась кнопка переключения рендерера в обе стороны (DLL переименовываются в .off, а не удаляются), а нативный вывод Mesa, драйверов и GLFW попадает в logs/native-stderr.log, а не в никуда. С флагом -tray не показывается ни один диалог и не трогается ни один файл. Смена рендерера теперь перезапускает лаунчер сама: OPENGL32.dll стоит в таблице импорта exe, Windows отображает его раньше любой строки нашего кода, и внутри живого процесса рендерер не сменить в принципе — первый RC на ответ «да, отключить Mesa» продолжал старт с уже отображённой Mesa и падал второй раз подряд. По той же причине проба железа теперь уводит локальный opengl32.dll в сторону на время своей работы и отбрасывает «аппаратный» ответ, у которого вендор GL оказался Mesa: раньше она выдавала мезовский D3D12 (NVIDIA GeForce GT 440) за саму видеокарту.

  • macOS: диалог выбора файла серит все файлы (#120). На macOS 26 системная панель открытия перестала понимать голое расширение как фильтр типа, и Import… на вкладке Settings вместе с Add from file показывали файлы бэкапов и конфигов недоступными — выбрать было нечего. Теперь лаунчер перед открытием панели резолвит каждое расширение в системный идентификатор типа; расширения без зарегистрированного типа (.vpn, .conf) получают динамический, который панель принимает. Если не резолвится ничего, панель открывается без фильтра, а не пустой.

  • Импорт бэкапа терял правила маршрутизации и Направление по умолчанию (#111). Отчёт импорта считал правила из файла применёнными, но до вкладки Rules доходили только пресетные: пользовательские и srs-правила писались в канонический список, а визард продолжал читать свой прежний вид этого списка. route.final из файла уходил в поле, которое визард не читает, и Default direction оставалось прежним; собственный экспорт лаунчера route.final не писал вовсе. Оба канала теперь те, которыми визард действительно пользуется.

  • Перетаскивание строки могло оставить всё окно глухим к кликам. Линия вставки, которая рисуется при перетаскивании строки (Sources, Rules, DNS, Направления, состав папки), живёт в стеке overlay-ев канваса, а снималась как «верхний, кто бы он ни был». Если посреди броска поверх неё успевало лечь что-то ещё, снималось оно, а обёртка линии оставалась навсегда — Fyne же отдаёт любой клик только верхнему overlay-у, так что окно продолжало рисоваться, но не реагировало на ввод при полностью живом процессе (два фриза 7–8 сентября, оба лечились перезапуском). Теперь обёртка снимается по ссылке, лежавшее поверх возвращается на место, а любое завершение броска и любая пересборка списка её убирают.

  • Узлы REALITY молча переставали работать на обновившихся серверах. Xray v26.9.8 ужесточил рукопожатие REALITY: сервер теперь ждёт в ClientHello постквантовый ключевой обмен, а из отпечатков uTLS его шлют только chrome-овские. Узлу, у которого в ссылке стояло fp=firefox — или fp=safari, ios, android, edge, 360, qq, — отвечал камуфляжный сайт вместо прокси: соединение выглядело установленным и просто не везло ничего. Ни ошибки в логе, ни повода придраться у sing-box check. Дефолтный random выбирал мёртвый отпечаток в четырёх случаях из пяти — отсюда и «вчера работало, сегодня нет» на одном и том же узле. Теперь везде, где лаунчер эмитит блок REALITY, он использует chrome-отпечаток; значение из ссылки остаётся видно на узле, а подмена отмечается строкой в отчёте сборки. Узлов, чьи серверы остались на старом Xray, это не касается — chrome-овский ClientHello принимают и те, и другие.

  • SRS-правило хранит все наборы, которые ему дали. Правило, собранное из нескольких ссылок на SRS, запоминало только первую: запись состояния держала один URL, поэтому после переоткрытия Конфигуратора остальные наборы исчезали, а сборка эмитила лишь первый набор даже в той сессии, где правило создали (репорт по 1.5.5). Теперь запись несёт полный список: правило восстанавливает все наборы, сборка эмитит по локальному rule-set на каждую ссылку и одно правило маршрута со ссылкой на все, а чистка сирот держит каждый скачанный файл. Бэкап тоже переносит весь список — полем refs рядом с прежним ref (черновое поле контракта: приложение, которое его не знает, по-прежнему получает первый набор).

Техническое / Внутреннее

  • Узел tailscale на ядре без тега снимается, а не валит сборку. Endpoint есть только в ядрах, собранных с with_tailscale (форк с 1.14.0-lx.31), и один такой узел заставляет sing-box check отвергнуть конфиг целиком — вместе со всеми остальными серверами. Лаунчер один раз за сборку опрашивает теги ядра и снимает такие узлы с предупреждением, называющим причину в отчёте сборки, — ровно как уже делает для naive. Нечитаемый вывод sing-box version по-прежнему считается «умеет»: узлы не снимаются по догадке.

  • Гейт AmneziaWG 3.x — по версии, а не по тегу. with_awg есть в ядре давно, но header_protection_key и соседи появились только в 1.14.0-lx.32, и ядро постарше отвергает как невалидный JSON весь конфиг, а не тот единственный узел, который не смогло прочитать. Поэтому лаунчер раз за сборку читает из sing-box version и теги, и версию: нет with_awg → AmneziaWG нет вовсе; базовая версия выше 1.14.0 → умеет; ровно 1.14.0 → решает номер релиза в суффиксе -lx.N. Нечитаемый вывод по-прежнему считается «умеет»: узлы не снимаются по догадке. Правило MTU не меняется: узел AWG 3.x ужимается до 1280, как любой endpoint AmneziaWG — на эталонном сервере 1376 из экспорта данных не давал, 1280 дал.

  • Вставленный wg-quick конфиг больше не теряет маскировку молча. Разбор INI переносил junk-числа и явные теги i1i5, но пропускал ip / id / ib: конфиг с маскировкой выглядел полностью настроенным, а первый decoy-пакет уходил без неё. Тот же сахар теперь сам по себе считается признаком AmneziaWG, из-за чего MTU такого узла ужимается до потолка AmneziaWG — без этого узел отказывает молча: рукопожатие проходит, данные не идут.

  • GET /debug/goroutines — дамп горутин без убийства лаунчера. Когда UI Fyne замирает, Go-часть обычно продолжает жить: debug API отвечает, часовой heartbeat пишется в лог. Увидеть, на чём стоит главный цикл, до сих пор было нечем: stderr лаунчера при запуске из Finder уходит в /dev/null, так что дамп по SIGQUIT теряется, а attach через lldb процесс просто убивает. Новый эндпоинт отдаёт тот же текст, что Go печатает по SIGQUIT, — стеки всех горутин как text/plain, число в заголовке X-Goroutines — не останавливая процесс. goroutine 1 — главный цикл Fyne/GLFW. Снимать дамп нужно до перезапуска зависшего лаунчера.

  • GET /debug/ui и POST /debug/ui/overlays/clear — посмотреть и снять стек overlay-ев. Снимок перечисляет по каждому окну размер канваса, виджет в фокусе и стек overlay-ев (тип, позиция, размер, дети) — ровно то место, где виден забытый overlay; очистка снимает все overlay-и и возвращает окно без перезапуска. 504 ui loop unresponsive означает, что заблокирован сам главный цикл Fyne, а это другая ошибка. Capability ui в манифесте.

Don't miss a new singbox-launcher release

NewReleases is sending notifications on new releases.