Files

228 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: удобная разработка bropicker с ИИ-агентом (Slint)
> Статус: принят к исполнению. Дата: 2026-08-25.
>
> Предыстория: эксперимент dioxus-native/Blitz закрыт (прозрачность окна на Windows
> нереализуема без форка blitz-shell/winit на этапе создания HWND). Ветка-архив:
> `dioxus-archive`. Работаем на Slint, основная ветка `main`.
## Принцип «агент видит картинку»
Цикл работы агента над дизайном:
```txt
правка 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 — порядок в репозитории
- [x] Удалена `example/` — копия примера Slint с копирайтом SixtyFPS и битыми path-deps
- [x] Zip `browser-logos` удалён (не был в git; перезаливаемо с github alrrr/browser-logos);
LICENSE логотипов сохранён в `logos/LICENSE-browser-logos.txt`;
в `logos/` уже лежат отобранные PNG (chrome, firefox, opera, yandex, brave)
- [x] `.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 build`
`Start-Process target\debug\bropicker.exe` → пауза ~1.5 c → shot.ps1 →
`Stop-Process`. Решает проблему «GUI-процесс блокирует терминал агента»
- [ ] Опционально: `slint-lsp` / VS Code extension — ей для редактора, агент не зависит
Проверка цикла: `render-refs``run-and-shot` → оба PNG существуют, агент их прочитал.
## Этап C — возвращение к no-frame окну (цель дизайна)
Текущее: окно с заголовком (специально, чтобы двигать мышью). Целевое: frameless +
прозрачность + драг за хедер, как в макете.
- [x] `no-frame: true` + `background: transparent` в MainWindow (+ `title: "bropicker"`)
- [x] Прозрачность подтверждена скриншотами: карточка со скруглёнными углами парит
над рабочим столом, тёмная и светлая темы
- [x] Драг: TouchArea на хедере (`pointer-event` down) → `request-drag`
`with_winit_window(drag_window)` через `i-slint-backend-winit`
- [x] Центрирование окна при старте — `src/winit.rs` восстановлен из истории
(осторожно: git-редирект PowerShell создаёт UTF-16 — файл перезаписан в UTF-8)
- [x] Esc — закрыть: `FocusScope.key-pressed` (в 1.16 нет `key-event`; обработчик
обязан вернуть `accept`/`reject` во всех ветках)
- [x] `BP_THEME=light` env-флаг + `run-and-shot.ps1 -Light`
- [x] README обновлён (стек, статус, команды dev-цикла, дорожная карта)
Требует ручной проверки: перетаскивание мышью, Esc, центрирование на активном мониторе.
## Этап D — список браузеров (MVP-ядро)
- [x] ListView + BrowserItem восстановлены в main.slint (список растягивается, карточки 84px)
- [x] Логотипы: PNG из `logos/`; `BrowserConfig.icon` теперь `image` — грузится в Rust
через `Image::load_from_path` (@image-url не умеет рантайм-пути; serde-derive
из types.slint убран — вернём в этапе E строкой в TOML)
- [x] Мок-данные 4 браузеров; selected-index = первый is_default
- [x] Выбор кликом + ring-подсветка + hover; галочка — SVG (`icons/check.svg`),
т.к. глифа U+2713 в шрифтах нет; добавлен токен `Theme.surface-item`
- [x] Чекбоксы «Запомнить выбор…»/«Всегда спрашивать» — кастомный CheckRow по макету
- [x] «Открыть» → `Command::new(path).args(flags).arg(url)`; при успехе окно скрывается
(loop завершается), при ошибке — сообщение в stderr
- [x] URL из argv (`std::env::args().nth(1)`)
Примечание: мок-пути (firefox.exe, chrome.exe, zen.exe) вне PATH — запуск вернёт
ошибку в stderr до этапа E (автодетект/конфиг). Клик-выбор проверяется визуально.
### Этап D2 — UX-полировка списка
- [x] Отступ списка слева/справа 12px (карточки не прилипают к краям)
- [x] Индикатор выбора без синего круга — синяя SVG-галочка 16px;
у невыбранных — шеврон «›»
- [x] Клик по карточке = немедленный запуск (`launch-browser(BrowserConfig)`);
«Открыть» в футере остаётся (запуск выбранного)
## Этап E — конфиг и настройки
### E1 — конфиг, автодетект, логика запуска — ГОТОВО
- [x] `src/config.rs`: `%APPDATA%\bropicker\config.toml` (крейсы `toml` + `winreg`);
env `BP_CONFIG` для тестов; секции `[[browsers]]`, `[remembered]` (домен→имя),
`[settings]` (remember_choice, always_ask)
- [x] Автодетект из реестра `HKLM/HKCU\SOFTWARE\Clients\StartMenuInternet`:
имя + exe из shell\open\command, дедуп по пути, проверка существования файла;
результат автосохраняется. Проверено: найдены Firefox, Zen, Chrome, Edge
- [x] Иконки по имени (firefox/chrome/opera/brave/yandex → `logos/*.png`,
остальное → globe.svg). Нет логотипов: Edge, Zen
- [x] Логика запуска: домен запомнен + always_ask=off → мгновенный запуск без окна
(протестировано); иначе пикер. Клик/«Открыть» + remember_choice → запись
домена в конфиг; переключение галочек персистится
- [x] URL-бар привязан к свойству (был захардкожен), метка чекбокса — домен
### E2 — страница настроек — ГОТОВО (отдельное окно)
- [x] Настройки = **отдельное окно** `SettingsWindow` (760×560, обычная рамка,
resizable; в маленьком пикере редактировать неудобно). То же событийное
кольцо; Theme-глобал красит оба окна; модели шарятся (ModelRc один на оба)
- [x] cog в пикере → показать окно (центрирование через invoke_from_event_loop —
winit-окно реализуется лениво, как и у пикера)
- [x] Две панели: браузеры (иконка/имя/путь/корзина) + запомненные домены
(domain → browser/корзина); кнопка «Автодетект» (мерж по пути)
- [x] Esc в настройках = закрыть окно; X работает по умолчанию
- [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 — доведение до ежедневного использования — ГОТОВО
- [x] cog → открыть конфиг в редакторе (subl → notepad)
- [x] Замер cold start: Instant в main → печать при старте event loop
- [x] `tools/register.ps1` (+ `-Uninstall`): **ставит пакет в
`%LOCALAPPDATA%\bropicker\` (exe + logos + icons)** и регистрирует
ProgId/Capabilities/RegisteredApplications в HKCU на путь из LOCALAPPDATA
(не target\ — переживает cargo clean и перенос репо), открывает
ms-settings:defaultapps
- [x] Иконки резолвятся от папки exe (иначе при запуске обработчиком ссылок
cwd = system32 и иконки бы отвалились); проверено запуском из
LOCALAPPDATA с cwd=System32
- [x] Release-профиль: lto + codegen-units=1 + strip
- [x] Release без консоли: `windows_subsystem="windows"` только для не-debug
(в debug консоль остаётся); весь вывод → `%APPDATA%\bropicker\bropicker.log`
(config::log с unix-timestamp)
- [x] Парсер флагов понимает кавычки (`split_flags`) — профили браузеров:
`flags = '--profile-directory="Profile 1"'` (Chrome), `-P "Work"` (Firefox);
пример: docs/config.example.toml
- [x] Эмодзи-иконка для записи: поле `emoji` в конфиге (приоритет над icon) —
различение профилей одного браузера; фолбэк-глобус colorize по теме
(был чёрным на тёмной), цветные PNG логотипов не трогаются
(`icon-mask` только для генерик-глобуса)
- [x] Cold start release: 131 ms (после добавления файлового лога)
## Этап F — релиз
- [x] `[profile.release]`: lto, codegen-units=1, strip
- [x] Замер cold start (release, до старта event loop): **183 ms холодный /
120-122 ms тёплый**; из LOCALAPPDATA первый запуск 153 ms.
Цель <100 ms чуть не достигнута; варианты оптимизации, если захочется:
renderer-femtovg вместо skia (быстрее init GPU), отложенная загрузка
конфига. Пока считаем приемлемым
- [x] Иконка exe: `assets/icon.svg` (градиент + глобус + точки «выбора»)
`magick``assets/icon.png` + `assets/app.ico` (16-256) → `winresource`
в build.rs; проверено ExtractAssociatedIcon
- [x] Лицензия MIT (`LICENSE`); Slint — по Royalty-free License 2.0 (маршрут (b):
бейдж Made-with-Slint в README, текст лицензии в `licenses/` и в
составе установки `%LOCALAPPDATA%\bropicker\licenses\`).
ВАЖНО: маршрут (b) требует публичной страницы с бейджем; если репо
станет приватным — добавить виджет AboutSlint в окно настроек
- [x] Скриншоты в README: `docs/img/screenshot-{dark,light}.png`;
перегенерация — `just screenshots` (tools/screenshots.ps1 → shot.ps1 -Card:
DPI-aware обрезка до карточки, клип на 2px внутрь срезает AA-бахрому
скруглений, углы прозрачные — чужих пикселей нет)
- [ ] Ручной сценарий приёмки: 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; манифесты руками — только человеком