Files
bropicker/docs/dev-workflow.md
T

16 KiB
Raw Blame History

План: удобная разработка bropicker с ИИ-агентом (Slint)

Статус: принят к исполнению. Дата: 2026-08-25.

Предыстория: эксперимент dioxus-native/Blitz закрыт (прозрачность окна на Windows нереализуема без форка blitz-shell/winit на этапе создания HWND). Ветка-архив: dioxus-archive. Работаем на Slint, основная ветка main.

Принцип «агент видит картинку»

Цикл работы агента над дизайном:

правка ui/*.slint → сборка+запуск в фоне → скриншот окна → сравнение с ref-PNG → правка

Агент читает PNG напрямую (инструмент чтения файлов поддерживает картинки), поэтому визуальная сверка с макетами происходит без участия человека.

Инструменты визуализации:

  • PS-скрипт скриншота окна (основной): ноль зависимостей, любая версия slint, снимает реальный композитинг — важно для прозрачности поверх стола. Минус: окно должно быть видимым
  • slint-snapshot (опционально, позже): headless, детерминированный, пиксель-диф из коробки. Минусы: требует ровно slint =1.17.1 (апгрейд с 1.16.1) и возню со шрифтами/embed ресурсов

HTML-макеты → PNG: headless Edge (--headless --screenshot), он предустановлен на Windows.


Этап A — порядок в репозитории

  • Удалена example/ — копия примера Slint с копирайтом SixtyFPS и битыми path-deps
  • Zip browser-logos удалён (не был в git; перезаливаемо с github alrrr/browser-logos); LICENSE логотипов сохранён в logos/LICENSE-browser-logos.txt; в logos/ уже лежат отобранные PNG (chrome, firefox, opera, yandex, brave)
  • .gitignore: tools/out/, *.log, coldstart.txt

Этап B — утилиты в tools/

Все скрипты — PowerShell, без сторонних зависимостей.

  • tools/render-refs.ps1 — рендерит refs/dark.html и refs/light.html через headless Edge в tools/out/ref-dark.png / ref-light.png (окно 488×800, --hide-scrollbars). Находит msedge.exe в стандартных путях
  • tools/shot.ps1 [-Title bropicker] [-Out tools/out/app.png] — скриншот окна приложения: FindWindowW + GetWindowRect (Win32 через Add-Type) + System.Drawing CopyFromScreen
  • tools/run-and-shot.ps1 — полный цикл агента: cargo buildStart-Process target\debug\bropicker.exe → пауза ~1.5 c → shot.ps1 → Stop-Process. Решает проблему «GUI-процесс блокирует терминал агента»
  • Опционально: slint-lsp / VS Code extension — ей для редактора, агент не зависит

Проверка цикла: render-refsrun-and-shot → оба PNG существуют, агент их прочитал.

Этап C — возвращение к no-frame окну (цель дизайна)

Текущее: окно с заголовком (специально, чтобы двигать мышью). Целевое: frameless + прозрачность + драг за хедер, как в макете.

  • no-frame: true + background: transparent в MainWindow (+ title: "bropicker")
  • Прозрачность подтверждена скриншотами: карточка со скруглёнными углами парит над рабочим столом, тёмная и светлая темы
  • Драг: TouchArea на хедере (pointer-event down) → request-dragwith_winit_window(drag_window) через i-slint-backend-winit
  • Центрирование окна при старте — src/winit.rs восстановлен из истории (осторожно: git-редирект PowerShell создаёт UTF-16 — файл перезаписан в UTF-8)
  • Esc — закрыть: FocusScope.key-pressed (в 1.16 нет key-event; обработчик обязан вернуть accept/reject во всех ветках)
  • BP_THEME=light env-флаг + run-and-shot.ps1 -Light
  • README обновлён (стек, статус, команды dev-цикла, дорожная карта)

Требует ручной проверки: перетаскивание мышью, Esc, центрирование на активном мониторе.

Этап D — список браузеров (MVP-ядро)

  • ListView + BrowserItem восстановлены в main.slint (список растягивается, карточки 84px)
  • Логотипы: PNG из logos/; BrowserConfig.icon теперь image — грузится в Rust через Image::load_from_path (@image-url не умеет рантайм-пути; serde-derive из types.slint убран — вернём в этапе E строкой в TOML)
  • Мок-данные 4 браузеров; selected-index = первый is_default
  • Выбор кликом + ring-подсветка + hover; галочка — SVG (icons/check.svg), т.к. глифа U+2713 в шрифтах нет; добавлен токен Theme.surface-item
  • Чекбоксы «Запомнить выбор…»/«Всегда спрашивать» — кастомный CheckRow по макету
  • «Открыть» → Command::new(path).args(flags).arg(url); при успехе окно скрывается (loop завершается), при ошибке — сообщение в stderr
  • URL из argv (std::env::args().nth(1))

Примечание: мок-пути (firefox.exe, chrome.exe, zen.exe) вне PATH — запуск вернёт ошибку в stderr до этапа E (автодетект/конфиг). Клик-выбор проверяется визуально.

Этап D2 — UX-полировка списка

  • Отступ списка слева/справа 12px (карточки не прилипают к краям)
  • Индикатор выбора без синего круга — синяя SVG-галочка 16px; у невыбранных — шеврон «›»
  • Клик по карточке = немедленный запуск (launch-browser(BrowserConfig)); «Открыть» в футере остаётся (запуск выбранного)

Этап E — конфиг и настройки

E1 — конфиг, автодетект, логика запуска — ГОТОВО

  • src/config.rs: %APPDATA%\bropicker\config.toml (крейсы toml + winreg); env BP_CONFIG для тестов; секции [[browsers]], [remembered] (домен→имя), [settings] (remember_choice, always_ask)
  • Автодетект из реестра HKLM/HKCU\SOFTWARE\Clients\StartMenuInternet: имя + exe из shell\open\command, дедуп по пути, проверка существования файла; результат автосохраняется. Проверено: найдены Firefox, Zen, Chrome, Edge
  • Иконки по имени (firefox/chrome/opera/brave/yandex → logos/*.png, остальное → globe.svg). Нет логотипов: Edge, Zen
  • Логика запуска: домен запомнен + always_ask=off → мгновенный запуск без окна (протестировано); иначе пикер. Клик/«Открыть» + remember_choice → запись домена в конфиг; переключение галочек персистится
  • URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен

E2 — страница настроек — ГОТОВО (отдельное окно)

  • Настройки = отдельное окно SettingsWindow (760×560, обычная рамка, resizable; в маленьком пикере редактировать неудобно). То же событийное кольцо; Theme-глобал красит оба окна; модели шарятся (ModelRc один на оба)
  • cog в пикере → показать окно (центрирование через invoke_from_event_loop — winit-окно реализуется лениво, как и у пикера)
  • Две панели: браузеры (иконка/имя/путь/корзина) + запомненные домены (domain → browser/корзина); кнопка «Автодетект» (мерж по пути)
  • Esc в настройках = закрыть окно; X работает по умолчанию
  • env BP_VIEW=settings — запуск сразу с окном настроек (без пикера); нюанс: run() авто-показывает своё окно, поэтому в этом режиме цикл крутится на settings_window
  • Редактирование браузера (имя/путь/флаги через LineEdit) — ОТЛОЖЕНО (решено: cog в пикере открывает config.toml в редакторе — subl, fallback notepad; если файла нет — создаётся с автодетектом). Окно SettingsWindow осталось в коде (недоступно из UI), dev-доступ: env BP_VIEW=settings
  • Чтение флагов из .lnk ярлыков (крейс lnk) — по желанию
  • Добавить логотипы Edge/Zen в logos/ (пересоздать набор из alrrr/browser-logos)

E3 — доведение до ежедневного использования — ГОТОВО

  • cog → открыть конфиг в редакторе (subl → notepad)
  • Замер cold start: Instant в main → печать при старте event loop
  • tools/register.ps1 (+ -Uninstall): ставит пакет в %LOCALAPPDATA%\bropicker\ (exe + logos + icons) и регистрирует ProgId/Capabilities/RegisteredApplications в HKCU на путь из LOCALAPPDATA (не target\ — переживает cargo clean и перенос репо), открывает ms-settings:defaultapps
  • Иконки резолвятся от папки exe (иначе при запуске обработчиком ссылок cwd = system32 и иконки бы отвалились); проверено запуском из LOCALAPPDATA с cwd=System32
  • Release-профиль: lto + codegen-units=1 + strip

Этап F — релиз

  • [profile.release]: lto, codegen-units=1, strip
  • Замер cold start (release, до старта event loop): 183 ms холодный / 120-122 ms тёплый; из LOCALAPPDATA первый запуск 153 ms. Цель <100 ms чуть не достигнута; варианты оптимизации, если захочется: renderer-femtovg вместо skia (быстрее init GPU), отложенная загрузка конфига. Пока считаем приемлемым
  • Иконка exe (.ico через build.rs winres)
  • Выбрать лицензию проекта: GPLv3 (опенсорс) или Royalty-free Slint (закрытый десктоп, атрибуция AboutSlint) — зафиксировать в README и LICENSE
  • Скриншоты в README из tools/out
  • Ручной сценарий приёмки: register → клик ссылки в другом приложении → пикер → выбор → вкладка открылась; повторный клик того же домена при always_ask=off → сразу браузер

Этап F — релиз

  • [profile.release]: lto, codegen-units=1, strip
  • Замер cold start: Instant в начале main → печать в первом колбэке; цель <100 ms; результат записать сюда: ___ ms
  • Иконка exe (.ico через build.rs winres)
  • Выбрать лицензию проекта: GPLv3 (опенсорс) или Royalty-free Slint (закрытый десктоп, атрибуция AboutSlint) — зафиксировать в README и LICENSE
  • Скриншоты в README из tools/out

Решение по архитектуре запуска (исследование 2026-08-25)

ОС вызывает пикер так: CreateProcess("bropicker.exe", "https://...") — новый процесс на каждый клик, постоянной связи с вызывающим приложением нет. Два режима:

  • Новый инстанс (выбрано для MVP): нулевая сложность; если cold start < 100 ms, разница с резидентом невидима
  • Резидент + трей (этап G, по показаниям): spawn-заглушка + IPC + показ готового окна (~5-10ms), но: постоянная память ~30-80MB, named mutex, IPC (named pipe / WM_COPYDATA), крейс tray-icon + скрытое winit-окно (в Slint трея нет), автозапуск

Критерий старта этапа G: замер этапа F показал cold start > 100 ms, либо нужен доступ к настройкам/функциям из трея. Single-instance guard (named mutex + фокус существующего окна) можно добавить отдельно, без полного резидента — если быстрые клики по нескольким ссылкам начнут плодить окна.

Этап G — резидент + трей (отложен до результатов этапа F)

  • Решение по итогам замера cold start (см. блок выше)
  • Single-instance guard: named mutex, форвард URL в существующий инстанс (named pipe)
  • Иконка в трее (tray-icon), меню: Открыть настройки / Выход
  • Автозапуск (HKCU Run или Startup-ярлык)

Заметки для агента

  • GUI-приложение запускать только через tools/run-and-shot.ps1, никогда не блокирующе
  • После каждого изменения UI: скриншот + сравнение с tools/out/ref-{dark,light}.png
  • Тёмная/светлая тема: переключение кнопкой sun/moon (Theme.toggle); для скриншота светлой темы — tools/run-and-shot.ps1 -Light (env BP_THEME=light)
  • Cargo.toml правится только через cargo CLI; манифесты руками — только человеком