feat(events): EventBus + domain-модель статусов
- internal/model: единый источник статусов задачи (Status, TraceStatus, машина переходов), без зависимости от storage. - internal/storage: совместимый мост (type Status = model.Status, re-export констант) — внешний код не меняется. - internal/events: шина событий (fan-out, блокирующий Publish с гарантией порядка), события задач/трейсов, отдельная логовая шина + LogWriter, Publisher/NilPublisher для внедрения в Core/Worker. - docs/ui-spec.md: спецификация десктопного UI (Fyne).
This commit is contained in:
160
docs/ui-spec.md
Normal file
160
docs/ui-spec.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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. Скоуп реализации (поэтапно)
|
||||
|
||||
- **Фаза 1**: `internal/ui` со сплитами + `TaskStore`-абстракция + **односторонний** поток: команды → Core, периодические snapshots из БД (без шины), всё через `fyne.Do`.
|
||||
- **Фаза 2**: добавить `event.Bus` — Core публикует события, UI подписывается (статусы, история, live-шаги).
|
||||
- **Фаза 3**: консистентность TG↔UI через общий маршрутизатор/шину (`UserID`, `chat.Router`).
|
||||
|
||||
---
|
||||
|
||||
## Открытые пункты (TODO)
|
||||
|
||||
- [ ] Точно определить set сплит-панелей (2x2 центр) и как добавляются
|
||||
- [ ] Обязательные поля формы создания задачи
|
||||
- [ ] Подробности виджета списка задач (после первого мильстон)
|
||||
- [x] Архитектура обмена UI<->Core (раздел 12 — принципы; детальные контракты событий/интерфейсов — следующая итерация)
|
||||
Reference in New Issue
Block a user