Files
portal/docs/superpowers/specs/2026-05-19-brain-dashboard-design.md
T
Дмитрий 47a257624a docs(brain): brain dashboard design spec
Standalone HTML dashboard that visualises the observer episode log over
the automation-graph topology — 4 views (map / task-replay / session
feed / aggregate), graph as shared canvas, 3-phase build order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:23:43 +03:00

20 KiB
Raw Blame History

Дашборд мозга — дизайн

Дата: 2026-05-19 Статус: согласован в brainstorming-сессии, готов к writing-plans Источник: brainstorming-сессия Claude + Дмитрий

1. Контекст и проблема

docs/automation-graph.html («карта мозга») — статический снимок: 124 узла, 130 рёбер, 11 размеченных конфликтов, плюс ручная теплокарта NODE_META за фиксированное окно. Карта показывает топологию на момент, но не показывает работу: как решались задачи, какие узлы задействованы, где возникли ошибки и ретраи, куда перенаправлялось, где сталкивались узлы.

Журнал исполнения уже существует — docs/observer/episodes-YYYY-MM.jsonl: наблюдатель (ADR-011) пишет одну запись на каждый ход (Stop-событие). В записи — выбранный узел, инструменты, ошибки, ретраи, перенаправления, hard-floor, классификация задачи, окружение. Данные есть — нет визуализации. Карта и журнал между собой не связаны.

2. Цель и не-цели

Цель: standalone-дашборд, визуализирующий журнал эпизодов поверх топологии карты — три способа смотреть на работу мозга плюс сама карта.

В scope:

  • Чтение episodes-*.jsonl (схема v1 и v2).
  • Четыре view: Карта, Разбор задачи, Лента сессии, Агрегат.
  • Граф как общий холст для траекторий и тепла.
  • Три слоя «конфликтов» (см. §6).

Не-цели (YAGNI):

  • Не меняем формат эпизодов и логику наблюдателя (исключение — отдельная задача, §13).
  • Не правим /brain-retro и контролёры.
  • Не пиксель-полировка — Forest накладывается на этапе реализации (frontend-design).
  • Нет истории до запуска наблюдателя — её физически нет.
  • Не Vue/Vuetify-приложение — это dev-инструмент, zero-build (см. §3).

3. Зафиксированные решения

Тех-модель: standalone HTML + локальный статик-сервер. Один HTML-файл без сборки; читает свежий JSONL через статик-сервер (fetch с file:// браузер блокирует). Сервер — ~20 строк на node:http, запуск npm-командой, гасится по Ctrl-C. Ноль новых npm-зависимостей, ноль постоянных демонов. Отвергнуты: «запекаемый файл» (генератор вшивает данные — лента не живая) и «Vue/Vuetify-приложение» (сборка + стек портала в dev-инструменте; vis.js всё равно не Vue).

Раскладка: граф баннером сверху. Постоянная полоса графа сверху, рабочая зона view снизу; переключатель view меняет нижнюю зону. Граф всегда виден; широкие таблицы и ленты снизу.

4. Архитектура

4.1. Слой данных

Источник — docs/observer/episodes-YYYY-MM.jsonl, append-only, одна строка = один эпизод.

Сервер tools/brain-dashboard-server.mjs:

  • статика из корня репо (HTML, JS, JSONL, automation-graph.html, vis.js);
  • эндпоинт GET /api/episodes → JSON-список имён файлов docs/observer/episodes-*.jsonl (дашборд не угадывает имена);
  • больше ничего; только localhost.

Парсер (JS, внутри дашборда):

  • читает каждую строку JSONL → объект эпизода;
  • нормализует v1 (строки без schema_version — нет decision_provenance / environment / task_size / prompt_signal, outcome уже проставлен) и v2 (schema_version: 2);
  • битые строки и строки-маркеры observer_error — пропускаются, ведётся счётчик «N пропущено»;
  • результат — нормализованный массив эпизодов единой формы (отсутствующие v1-поля → null).

Производные данные (тепло, кластеры, агрегаты) считаются в браузере при загрузке. Ноль вшитых данных → всегда свежо.

4.2. Карта = общий холст

Сейчас топология зашита константами внутри automation-graph.html (один файл ≈2900 строк): NODES (стр. 229), EDGES (стр. 418), секции, CONFLICT-данные (стр. ≈406–614), NODE_META (стр. 1898), NODE_SECTION (стр. 2155).

Рефактор-вынос: константы топологии (NODES, EDGES, SECTIONS, CONFLICT-данные, NODE_SECTION) выносятся в docs/automation-graph-data.js. Старая карта <script src>-ит его — поведение и вид не меняются (подтвердить визуальным smoke-тестом). NODE_META (ручная теплокарта) остаётся в старой карте — дашборд её не использует, он считает тепло из эпизодов.

Дашборд импортирует automation-graph-data.js и строит свой экземпляр vis.js-графа в баннере. Этот граф управляемый — на нём анимируются траектории (Разбор) и красится тепло (Агрегат). Iframe старой карты отвергнут: чужой iframe нельзя анимировать снаружи.

Вкладка «Карта» = тот же граф дашборда в режиме «без оверлея». Файл automation-graph.html продолжает существовать как самостоятельная голая карта.

4.3. Атрибуция узлов (честная)

Граф — 124 узла (MCP-серверы, плагины, скилы, инструменты, секции). Эпизод даёт сигналы об узлах:

  • primary_rationale.node_chosen — чаще всего "direct", иногда id скила ("superpowers:systematic-debugging");
  • события skill_invoked — id скилов;
  • tool_summary.counts — имена встроенных инструментов Claude (Read, Edit, Bash, Grep, …) и Skill / ToolSearch.

Маршрутизируемый словарь наблюдателя — tools/observer-known-nodes.txt (~22 имени: 13 superpowers-скилов + 7 проектных + 2 плагина/команды). Это меньше 124 узлов графа и пересекается с ними частично.

Решение: дашборд держит таблицу соответствия сигнал эпизода → id узла графа. Подсвечивает узлы, для которых соответствие есть. Узлы без соответствия (встроенные инструменты Claude, большинство MCP/плагинов, которые эпизоды пока не называют) остаются неподсвеченными — это ожидаемо и подписано в UI («атрибутировано N из M сигналов»). Полнее станет, когда роутер начнёт писать node_chosen детальнее — вне scope этой задачи.

5. Четыре view

Общий каркас (раскладка из §3): сверху переключатель view + граф-баннер; снизу — рабочая зона.

5.1. Карта

Граф без оверлея: топология + 11 размеченных дизайн-конфликтов (цвета RED / BLACK / GREEN из CONFLICT_TYPES). Фильтры по секциям/типам — переносятся из старой карты по возможности (или минимальный набор). Это «нулевое состояние» холста.

5.2. Разбор задачи (ретроспектива)

Нижняя зона: слева список эпизодов (фильтр: дата, task_classification, outcome, path_type, наличие ошибок); справа — детали выбранного.

Выбор эпизода → траектория:

  • на графе подсвечиваются атрибутированные узлы (§4.3);
  • справа — упорядоченный список событий эпизода (skill_invoked / error / retry / hook_fired / interrupt / time_burn), плюс шапка: классификация, path_type, decision_provenance (если user_directed_method — «перенаправление: выбран X, автономно был бы Y»), hard_floor, окружение (economy_level, model, post_compaction, session_turn, parallel_session), task_size.

Честно: внутри хода есть упорядоченный список событий, но не каждый tool-вызов по порядку — tool_summary даёт только счётчики. «Траектория» = последовательность событий + сводка инструментов.

5.3. Лента сессии (живая)

Нижняя зона: одноколоночный поток эпизодов, сгруппированных по task_id / task_ref, новый ход сверху. Карточка хода: время, классификация, path_type, атрибутированный узел, ошибки/ретраи (бейджи), длительность (ended_at started_at), флаг перенаправления.

Автоопрос: дашборд раз в N секунд (по умолчанию 5) перезапрашивает /api/episodes + текущий месячный JSONL, дописывает новые строки. Полл — только в этом view. Кнопка пауза/возобновить.

5.4. Агрегат (тренды)

Нижняя зона: плитки метрик по всем эпизодам:

  • тепло узлов (авто — сколько раз каждый атрибутированный узел встречался); красит граф-баннер;
  • горячие точки ошибок/ретраев (узлы и классы задач с наибольшей долей error / retry);
  • доля перенаправлений (decision_provenance.kind == "user_directed_method");
  • распределения economy_level, path_type (improvised/regulated), task_classification, outcome;
  • счётчик observer_error и пропущенных строк.

Опциональный reuse: для v2-эпизодов с outcome: "unknown" — переиспользовать детерминированный inference из tools/brain-retro-analyzer.mjs, если он оформлен импортируемым модулем; иначе показывать unknown. Решается в плане.

6. Конфликты — три слоя

Запрос — «где конфликты среди узлов». Эпизоды не пишут «узел A столкнулся с узлом B» — только error / retry / hook_fired.errors. Дашборд отдаёт три явно подписанных слоя:

  1. Дизайн-конфликты — 11 размеченных CONFLICT-рёбер карты (факт, из топологии).
  2. Трение — эпизоды с error/retry, привязанные к атрибутированным в них узлам. Это инференс («во время хода с этим узлом была ошибка»), не доказанный конфликт. Подписано.
  3. Корреляция — эпизод с ошибкой, где атрибутированы два узла, между которыми есть CONFLICT-ребро → «конфликт мог реализоваться». Эвристика. Подписано.

Настоящего лога «узел×узел» нет. См. §13.

7. Раскладка

┌─────────────────────────────────────────────┐
│  [Карта] [Разбор] [Лента] [Агрегат]          │  переключатель view
├─────────────────────────────────────────────┤
│                                             │
│            ГРАФ — баннер (vis.js)           │  ~40% высоты, всегда виден
│                                             │
├─────────────────────────────────────────────┤
│                                             │
│         рабочая зона view (меняется)        │  ~60% высоты
│                                             │
└─────────────────────────────────────────────┘

Граф-баннер общий для всех view; рабочая зона своя у каждого. Forest-палитра (Teal #0F6E56, ivory #F6F3EC, теало-нуар #012019), Inter / JetBrains Mono — накладываются на этапе реализации как CSS-переменные.

8. Файлы и компоненты

Файл Назначение Статус
docs/observer/dashboard.html каркас дашборда (раскладка из §3/§7, vis.js-граф) новый
docs/observer/dashboard.js парсер JSONL + агрегатор + 4 view + рендер графа новый
docs/automation-graph-data.js вынесенная топология (NODES / EDGES / SECTIONS / CONFLICT / NODE_SECTION) новый (вынос)
docs/automation-graph.html <script src> на data-файл; остальное без изменений правка
tools/brain-dashboard-server.mjs статик-сервер + /api/episodes новый
package.json скрипт brain:dashboard (запуск сервера + открытие браузера) правка
tools/brain-dashboard-*.test.mjs тесты парсера / агрегатора / сервера новый

dashboard.js при росте можно разбить на модули (parser.js, aggregate.js, graph.js, views/*.js) — решается в плане по фактическому размеру.

9. Обработка ошибок и граничные случаи

  • Битая JSONL-строка / observer_error-маркер → пропуск, инкремент счётчика, показ счётчика в UI.
  • Месячный файл отсутствует или пуст → не ошибка.
  • Эпизодов нет вообще → дружелюбное пустое состояние.
  • v1-эпизод (нет v2-полей) → недостающие поля null, UI показывает «—».
  • Сервер не запущен → дашборд физически не откроется (он отдаётся этим же сервером); пустой /api/episodes → пустое состояние.
  • automation-graph-data.js не загрузился → пустой граф + явное сообщение.

10. Тестирование

  • TDD на чистую логику (dashboard.js — парсер, нормализация v1/v2, агрегатор, атрибуция узлов, инференс конфликтов): tools/brain-dashboard-*.test.mjs на node:test, failing-first → GREEN. Паттерн — как существующие tools/*.test.mjs.
  • Сервер brain-dashboard-server.mjs — smoke-тест (поднять, дёрнуть /api/episodes, проверить отдачу статики).
  • Вынос топологии — визуальный smoke старой карты (Playwright или ручной): карта выглядит и фильтруется как до выноса.
  • Рендер view и графа — ручной визуальный smoke в браузере.

11. Честные ограничения

  1. Гранулярность — один эпизод на ход. Внутри хода есть упорядоченный список событий, но не каждый tool-вызов по порядку (только счётчики tool_summary).
  2. «Живость» — после Stop, не в процессе. Лента обновляется после завершения хода (+ задержка автоопроса), не пока ход идёт.
  3. Атрибуция узлов частичнаяnode_chosen чаще direct; словарь наблюдателя ~22 имени против 124 узлов графа. Бóльшая часть графа не подсвечивается. См. §4.3.
  4. Конфликты узел×узел не логируются — даётся инференс (§6), не факт.
  5. История — только с запуска наблюдателя (~19.05.2026). Раньше эпизодов нет.

12. Порядок сборки (3 фазы, один спек)

  • Фаза 1 — фундамент. Статик-сервер + /api/episodes; вынос топологии в automation-graph-data.js + правка старой карты + визуальный smoke; каркас dashboard.html; парсер v1/v2 + атрибуция узлов (TDD); граф-баннер; view «Карта» + view «Разбор задачи»; npm-скрипт brain:dashboard.
  • Фаза 2 — живость. View «Лента сессии» + автоопрос/пауза.
  • Фаза 3 — агрегат. View «Агрегат» + тепло на граф-баннер + три слоя конфликтов (§6); опц. reuse brain-retro-analyzer.mjs для outcome-инференса.

13. Открытые вопросы / отложено

  • Настоящий лог конфликтов узел×узел — потребует нового типа события в наблюдателе (tools/observer-transcript-parser.mjs + схема эпизода). Отдельная задача, не входит в этот спек.
  • Двойной клик без сервера — если когда-нибудь понадобится: добавить генератору запекание данных, файл выродится в тех-модель «запекаемый файл» без переписывания. Сейчас YAGNI.
  • Forest-полировка — этап реализации (frontend-design).
  • vis.js — откуда его берёт старая карта (CDN или вендорено) — уточнить в плане, дашборд переиспользует тот же способ.