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>
20 KiB
Дашборд мозга — дизайн
Дата: 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. Дашборд отдаёт три явно подписанных слоя:
- Дизайн-конфликты — 11 размеченных
CONFLICT-рёбер карты (факт, из топологии). - Трение — эпизоды с
error/retry, привязанные к атрибутированным в них узлам. Это инференс («во время хода с этим узлом была ошибка»), не доказанный конфликт. Подписано. - Корреляция — эпизод с ошибкой, где атрибутированы два узла, между которыми есть
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. Честные ограничения
- Гранулярность — один эпизод на ход. Внутри хода есть упорядоченный список событий, но не каждый tool-вызов по порядку (только счётчики
tool_summary). - «Живость» — после Stop, не в процессе. Лента обновляется после завершения хода (+ задержка автоопроса), не пока ход идёт.
- Атрибуция узлов частичная —
node_chosenчащеdirect; словарь наблюдателя ~22 имени против 124 узлов графа. Бóльшая часть графа не подсвечивается. См. §4.3. - Конфликты узел×узел не логируются — даётся инференс (§6), не факт.
- История — только с запуска наблюдателя (~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 или вендорено) — уточнить в плане, дашборд переиспользует тот же способ.