Files
ratatoskr-go/docs/ui-spec.md
ki.sagidullin fdd73cab61 refactor(ui): вынести панель «Логи» (LogPanel) из монолита окна
Спеца 12.8-12.9: интерфейс + Nil в internal/ui/log_panel.go, Fyne-реализация
в internal/ui/desktop/log_panel.go, окно использует контракт вместо logsLbl.
2026-08-20 23:26:18 +05:00

188 lines
16 KiB
Markdown
Raw 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.
# UI-спека Ratatoskr (Fyne Desktop)
Статус: утверждается. Черновик, обсуждаем дальше (архитектура обмена UI <-> Core).
Стек: **Go + Fyne** (свежая стабильная версия). Платформа: **Windows** (Linux пока не поддерживаем).
---
## 1. Цель и роль UI
1. UI — **полноценный инструмент** (не только админ-панель): управляет всем, чем управляет Telegram, и становится **основным** интерфейсом.
2. Один пользователь, один человек (без multi-user / мультисессии).
3. UI должен уметь **всё, что умеет Telegram-интерфейс**, и станет основным; Telegram остаётся как один из каналов.
4. UI — **графическая оболочка самого приложения** (одна программа), не отдельный process.
5. Внутри оболочки запускается **ботик сам**; UI — это лишь ещё одна реализация `chat.Channel`.
6. Запуск: **обычный запуск бинаря** → UI. Режим без UI — только по флагу **`--noui`**.
- Логика: UI включён по умолчанию, headless — опционально (`--noui`).
7. Трей-иконка не нужна.
8. При закрытии окна — **сворачивать** (не завершать процесс). Для полного выхода — **отдельная кнопка** «Завершить», чтобы бот остановился.
9. Конфиг-секции редактировать в UI пока не нужно; **hot-reload конфига** — желательно, если реализуемо (запланировать как «если возможно — да»).
## 2. Компоновка (layout)
- Базовый grid по умолчанию:
- **слева — список задач** (широкая колонка),
- **по центру/право — рабочая область** (в ней живёт «работа бота» / live-шаги);
- рабочая область может **разделяться на сплит-панели** (2×2, не считая левой колонки с задачами).
- Табы пока **не делаем** (первый этап).
- Панели должны быть **сворачиваемыми** (хотя бы левая колонка с задачами).
- Сохранение layout (положение/размеры панелей) — **нужно**, переживает перезапуск приложения (Fyne Preferences).
- Окно: **на весь экран по умолчанию**.
- Нижняя панель: **вкладки «Логи», «Состояние»**:
- **Логи** — ведение логов (поток системных логов);
- **Состояние** — живое состояние текущих задач / на каком статусе сейчас задача.
- Верхняя панель: **меню**, пока только пункт «О программе» (About → About).
- Тема: **тёмная**, без переключателя темы; стартуем с **дефолтной темы Fyne**.
- Локаль: **en** (интерфейс на английском).
## 3. Данные задач
- Задачи отображаются **списком** слева: **краткое название + служебное** (воркшоп).
- Детализация списка (статус/фильтры/поиск/сортировка/по умолчание/пагинация) — **дорабатываем потом**.
- В деталях задачи (рабочая область) показываем все перечисленные поля:
- Title, Goal, Why, AC (acceptance criteria), Repo(s), Tag (task_tag), статус, CreatedAt/UpdatedAt, вложенные трассы (traces), история чата (history), включая (session id opencode).
- История чата из БД (user/assistant) — лента сообщений.
- Трейсы агентов (аналист/dev/reviewer) — дерево шагов/ходов + raw output.
- Live-шаги (LiveRegistry) — восстановить и показывать в реальном времени.
## 4. Действия с задачами
- UI поддерживает все действия, доступные в TG: создание задачи, изменение, **approve**, **rework доработка**, **cancel**, **retry**, закрытие (skip/continue), статусы — в рамках валидных переходов.
- Создание задачи — форма с полями (Title, Goal/Why, Repos, AC) (какие обязательные — уточнить).
- Работа с статусами: эмуляция команд `/approve`, `/retry`, `/cancel` и т.п. (валидные переходы по state machine)
- Подтверждения опасных действий (удаление задачи, и т.п.).
- Редактирование полей (repo, AC, tags) из UI — по необходимости.
## 7. Мониторинг системы
- Показывать **логи** (готовый поток из `log`), **фреймы состояния** бота:
- статус opencode serve/pool, коннекции, активные сессии.
- Live-статус задач (`/status N`) эквивалентом — во вкладке «Состояние».
- Нужен индикатор занятости агента («опенкод думает/dev writing…»).
## 8. Настройки / конфиг
- Настройки в UI пока **не выводим**; секреты никоим образом не показываем.
- Кнопка «Завершить» (выход приложения) — в меню.
- Перезапуск бота из UI — пока не надо.
## 9. Живое обновление / консистентность
- **Шина событий (event-bus)** — запланирована: UI получает события из ядра (новые задачи, изменение статусов, live-шаги, логи). Явно нужен.
- UI и Telegram — **оболочки** вокруг одного ядра; обмен через **интерфейс (абстракцию)**, не зашумляя ядро.
- Консистентность между TG и UI при параллельных изменениях — через события ядра; архитектура обмена — см. раздел 12.
## 10. Технические
- **Fyne**: свежая стабильная (последняя).
- **Абстракция БД**: доступ к storage через **интерфейс** (не прямиком к *storage.Storage) для тестируемости.
- Пакет: **`internal/ui`** (+ подпакеты при необходимости).
- Тесты: юнит-тесты модели, **без e2e/UI-тестов** пока.
- Использование данных: **вся история** (без ограничения по времени).
## 11. Скоуп первого этапа
- Минимальный UI, покрывающий **функционал Telegram-интерфейса** (все команды/действия).
- Также UI — основной интерфейс (TG остаётся каналом).
---
## 12. Архитектура обмена UI ↔ Core (best practices)
### 12.1. Слои и направление зависимостей
```
┌──────────────┐ команды → ┌─────────────────┐ заказы/готовое → ┌──────────────┐
│ UI (Fyne) │ ───────────▶ │ Application / │ ─────────────────▶ │ Domain/core │
│ «представ- │ │ UseCase слой │ события (events) │ (model) │
│ ление» │ ◀─────────── │ │ ◀────────────────── │ │
└──────────────┘ события ← └─────────────────┘ └──────────────┘
```
- **Core** (`internal/core`.ProcessTurn — state-machine) **не трогаем**. Он — источник правды.
- **App** (`internal/app`) — use-case/композиция: политика «одна активная задача на чат», `Notify`, роутинг команд.
- **UI** — ещё одна реализация `chat.Channel` (параллельно Telegram). Уже есть идеальный «port»: интерфейс `Channel{Run, OnMessage, Send, Ask, Close}`.
### 12.2. UI получает состояние, а не управляет Core'ом (uni-directional data flow)
- Core **мутирует состояние** (БД, worker, аналитик). UI — только **читает снимки** и реагирует на события, обновляя своё view-model (не БД и не core).
- Действия UI = **команды** (`CreateTask`, `ApproveTask`, `RetryTask`, `CancelTask`, `ContinueTask`), которые вызывают use-case в Core.
- Это даёт: единый источник правды, простую отладку, лёгкое тестирование без UI.
### 12.3. Обмен = событийная шина (event bus / pub-sub)
- **Core — публикует** доменные события, UI — **подписчик**:
- `TaskCreated{id, chatID}`
- `TaskStatusChanged{id, from, to}` (ready/approved/running/success/failed/timeout…)
- `TaskHistoryAppended{id, role, content}`
- `TraceAppended{id, agent, status, output}`
- `AgentActivity{id, agent, stage}` (live-шаги, индикаторы занятости)
- `LogLine` (системные логи для вкладки «Логи»)
- **Направление однонаправленное**: core/worker/analyst не знают, кто подписан; никаких прямых вызовов Fyne из core!
- Реализация малая: `Bus` с `Subscribe/Unsubscribe/Publish` + buffered channels (`n`-подписчиков или `select`); паттерн уже есть в `chat.Router` (внутренний `incoming` channel).
- Сигнатуры событий использовать **и для консистентности** TG↔UI (оба канала — простые подписчики/клиенты шины).
### 12.4. Модель потоков Fyne (v2.6+) и `fyne.Do`
- Fyne с v2.6.0 выполняет все события/колбэки **на одной главной goroutine**.
- Фоновые goroutine (worker, opencode polling, ход core) **не должны модифицировать UI напрямую**.
- Любое обновление виджетов из своей goroutine — через:
- `fyne.Do(func(){ widget.X = …; widget.Refresh() })` — очередь в следующий кадр;
- `fyne.DoAndWait(...)` — когда надо дождаться завершения;
- унифицировать с **binding** (`binding.String`, `binding.List`) — через `fyne.Do` не требуется, но проще явно.
- Подписчик шины ставит snapshot в очередь UI-обновления и вызывает `fyne.Do`.
### 12.5. Чтение-модель на UI: снапшоты, не мутабельные указатели
- UI получает **копии** записей (Task, history, traces) через абстракцию БД (см. раздел 10).
- Никаких референсов на разделяемые контейнеры core; используются лёгкие view-копии (Value-типы/DT-коды) — нет гонок bottom-up.
### 12.6. Жизненный цикл: Core ≠ UI
- Core (бот) живёт независимо от окна. Закрытие окна = **сворачивание** (Core продолжает).
- Полный выход — только кнопка «Завершить» → `app.Quit()` (Worker.Stop → router.Close → pool.Close → store.Close).
- `--noui` → core запускается без создания окна.
### 12.7. Скоуп реализации (поэтапно)
- [x] **Фаза 1**: `internal/ui` — Store-абстракция (копии), окно как `chat.Channel`,
команды через канал, snapshots из БД. Fyne-реализация — `internal/ui/desktop`
(build-tag `cgo`, требует компилятор C для GLFW).
- [x] **Фаза 2**: `internal/events` — Bus/Hub, Core публикует события, UI подписывается
через `Controller` (статусы, история, live-шаги, логи).
- [ ] **Фаза 3**: консистентность TG↔UI через общий маршрутизатор/шину (`UserID`, `chat.Router`).
Примечание: окно (Фаза 1) собирается только с cgo; на машинах без C-компилятора
приложение работает headless (UI=nil). Реальное окно полноценно проверяется
на машине с MinGW-w64/gcc (`go build -ldflags ...`), здесь — только typecheck.
### 12.8. Структура UI-кода: компоненты-виджеты (рефакторинг)
`internal/ui` (без Fyne) — контракты + Nil-реализации + юнит-тесты. Fyne-код —
только в `internal/ui/desktop/*`, один файл — одна панель. `desktop/window.go`
становится чистой компоновкой.
Каждый компонент:
- **интерфейс** в `internal/ui/<panel>.go` (контракт для тестов/headless);
- **Nil-реализация** в том же файле (headless/тесты);
- **Fyne-реализация** в `internal/ui/desktop/<panel>.go`.
### 12.9. Контракт виджета «Логи» (LogPanel)
Панель «Логи» — поток системных логов (сырые строки из `events.LogLine`).
- `Append(text)` — добавить строку лога; при превышении лимита старые строки
отбрасываются (буфер ограничен, не расти бесконечно).
- Контейнер виджета — отдаётся окну для встраивания в таб «Логи» (Scroll).
- Обновление содержимого — на потоке Fyne (внутри панели `fyne.Do`), не из
горутины Hub.
---
## Открытые пункты (TODO)
- [ ] Точно определить set сплит-панелей (2x2 центр) и как добавляются
- [ ] Обязательные поля формы создания задачи
- [ ] Подробности виджета списка задач (после первого мильстон)
- [x] Архитектура обмена UI<->Core (раздел 12 — принципы; детальные контракты событий/интерфейсов — следующая итерация)