Files
ratatoskr-go/docs/ui-spec.md
ki.sagidullin c2272137b3
Some checks failed
CI / test (pull_request) Failing after 1m19s
CI / build-and-package (amd64, linux) (pull_request) Failing after 58s
CI / build-and-package (amd64, windows) (pull_request) Successful in 53s
feat(ui): команды, snapshots, Fyne-окно и интеграция (--noui)
Команды (спец 12.2):
- ui.Commands: действия UI → текстовые команды канала (start/cancel/skip/
  retry/continue/approve/send), единый путь через chat.Channel.
- ui.Window = chat.Channel + View; NilWindow для headless/тестов.

Snapshots (спец 12.5):
- ui.Store: чтение-модель, возвращает только копии (ListTasks/GetTask/
  GetHistory/GetTraces); ui.DBStore поверх storage.

Fyne-окно (internal/ui/desktop, build-tag cgo):
- список задач слева, сплиты рабочей области и панели «Логи»/«Состояние»,
  ввод+кнопки команд, тёмная тема, fullscreen, сохранение layout в
  Preferences, сворачивание при закрытии крестиком.
- Колбэки View через fyne.Do (спец 12.4).

Интеграция:
- app.New(..., noUI); флаг --noui; UI собирается только с cgo (ui_cgo/
  ui_noui фабрики), приложение headless без него.
- Run: окно блокирует главную горутину; «Завершить» → cancel → graceful
  shutdown (спец 12.6).
- go.mod: fyne.io/fyne/v2 v2.6.0 (direct).
2026-08-20 08:54:55 +05:00

14 KiB
Raw Permalink Blame History

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 — Store-абстракция (копии), окно как chat.Channel, команды через канал, snapshots из БД. Fyne-реализация — internal/ui/desktop (build-tag cgo, требует компилятор C для GLFW).
  • Фаза 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.


Открытые пункты (TODO)

  • Точно определить set сплит-панелей (2x2 центр) и как добавляются
  • Обязательные поля формы создания задачи
  • Подробности виджета списка задач (после первого мильстон)
  • Архитектура обмена UI<->Core (раздел 12 — принципы; детальные контракты событий/интерфейсов — следующая итерация)