Плагин MEL для Android Studio
Тул-виндоу, который показывает MEL-экраны как стейт-машины: каталог слева, интерактивная диаграмма состояний справа, прыжок в код по клику. Этот документ — вход для дизайн-эксплорации: что плагин уже умеет, что мог бы уметь, на чём всё нарисовано.
О документе
Помимо ядра (:core:mel) и detekt-набора (:tools:detekt-rules) у MEL
есть IDE-плагин — модуль :tools:mel-idea-plugin на платформе IntelliJ. Он уже
собирается и работает: рисует граф экрана своим Java2D-рендерером (без JCEF) и навигирует в
исходник. Дальше его хочется развивать — и сперва спроектировать визуально.
Документ написан, чтобы лечь в проект Claude Design, где по нему будут собираться варианты дизайна интерфейса плагина. Поэтому он самодостаточен: дизайнеру, который никогда не видел MEL, его хватит, чтобы понять предметную область, текущие возможности и технические рамки.
Источник истины по архитектуре MEL — код в :core:mel, затем
спека; учебное введение — руководство.
Ключевые идеи MEL
MEL — архитектура UI для Kotlin/Android, где каждый экран описан как маленькая явная стейт-машина в духе Elm: экран всегда ровно в одном именованном состоянии, действия пользователя и внешние события приходят внутрь как «интенты», а маленькие чистые функции решают, каким будет следующее состояние. Поскольку все состояния, события и переходы объявлены в коде заранее, целый экран читается (и рисуется) как конечный граф из коробок и стрелок: состояния — узлы, интенты — подписи на стрелках, побочные эффекты вроде навигации висят на рёбрах.
Экран как стейт-машина (Elm-like)
Каждый экран — одна маленькая машина, которая всегда ровно в одном именованном состоянии.
Экран задаётся классом MelMachine со start() и графом правил-переходов.
В любой момент экран сидит на верхнем состоянии; пришедший интент проходит через подходящее
правило, которое возвращает следующее состояние. Никакой разбросанной мутабельной UI-логики — всё
поведение это один цикл «состояние → интент → следующее состояние».
На графе: это и есть холст — весь экран превращается в один конечный граф, где коробки это состояния, стрелки это переходы, и есть один явно отмеченный стартовый узел.
Контракт состояний (sealed)
Закрытый конечный список всего, что экран может показывать.
Состояния реализуют маркер MelState и пишутся как sealed-интерфейс —
Loading, Content, Confirm, Error. Sealed
значит множество закрыто и известно на компиляции, все возможные состояния перечислимы. Верх
стека — состояние, которое сейчас на экране.
На графе: sealed-список — это точный и полный набор узлов; ничего не скрыто и не появляется динамически, так что инструмент рисует по коробке на состояние и уверен, что собрал их все.
Факты (поля состояния)
Данные, которые состояние несёт, пока показывается — его содержимое, а не личность.
Состояния это data-классы, поля которых держат данные экрана:
Content(doc, isReloading=false), Confirm(doc, isSigning=false). Даже
то, что старые подходы считали разовым эффектом (шиммер, спиннер), становится булевым фактом.
Факты-MelFacts группируются в переиспользуемые наборы.
На графе: факты это детализация внутри коробки узла — инструмент аннотирует состояние его полями/флагами, и видно, какие данные и под-состояния (загрузка, ошибка) живут внутри одного состояния.
Интенты (события)
Всё, что может случиться с экраном, в виде закрытого списка событий.
Интенты реализуют MelIntent, обычно тоже sealed: SignClick,
Loaded(doc), Refresh, LoadFailed. Это тапы, ответы сервера,
тики таймера — любой вход. Интенты подаются через MelModel.send(intent) и это
единственный способ двигать машину вперёд.
На графе: интенты это подписи стрелок — каждое ребро между двумя состояниями названо интентом, который его запускает, и граф читается как «в состоянии X событие Y ведёт в состояние Z».
Reduce-клетки (on<State,Intent>{})
Одно правило: в ЭТОМ состоянии, на ЭТО событие, сделать ВОТ ЭТО.
Блок graph{} регистрирует клетки on<SS, II> { state, intent -> ... },
каждая ключуется парой (класс-состояния, класс-интента) и возвращает Transition.
Рантайм берёт самую специфичную подходящую клетку; дубликаты на одну пару отвергаются на сборке.
onAny<Intent> матчит любое состояние.
На графе: каждая клетка это буквально одно ребро — пара (исходное состояние, интент) с целью. Перечисление клеток даёт полный список смежности, чтобы нарисовать все стрелки.
Операции стека (next / push / pop / stay)
Четыре хода, которые правило может сделать со стеком состояний.
Клетка возвращает Transition с одной StackOp: next(to)
заменяет верх, push(to) кладёт слой сверху, pop() снимает верх,
popTo<Target>() разматывает к прежнему состоянию, stay() оставляет
то же состояние (удобно просто выстрелить эффектом). Других способов менять стек нет.
На графе: операция задаёт ребру смысл и стиль — next обычный
переход, push/pop слоистые ходы, stay самопетля; рёбра
красятся и гнутся по типу операции.
Стек состояний и оверлеи
Состояния складываются как карты; нижние карты остаются живыми под верхними.
Рантайм держит List<S>, а не одно состояние; push добавляет
слой, pop возвращает к нижнему (он жив, его подписки работают). В Compose
view<S> рисует верхнее нормальное состояние, а overlay<S> —
состояния поверх базы (диалоги, шторки подтверждения). Кнопка «назад» делает pop при
глубине > 1.
На графе: стек добавляет второе измерение — можно показать базовые состояния и оверлеи как слоистый под-граф, а рёбра push/pop показывают, какие состояния открываются ПОВЕРХ других, а не заменяют их.
Команды (асинхронная разовая работа)
Фоновая работа «выстрелил и забыл», которая возвращается новым интентом.
К переходу можно прицепить Command: suspend-блок делает асинхронную работу (сеть,
БД) и возвращает интент, который снова входит в машину; ошибки уходят через onError
тоже в интент. CommandKey позволяет новой команде отменить/заменить выполняющуюся
(дедуп), а переходы могут явно отменять команды по ключу. commandWithProgress умеет
слать промежуточные интенты.
На графе: команды объясняют, почему переход спустя задержку ведёт к другому
переходу — рисуется пунктирное «async»-ребро от состояния-инициатора к интенту (например
Loaded), который оно потом подаст обратно, обнажая циклы запрос/ответ.
Подписки (горячие потоки по состояниям)
Живые внешние потоки, работающие только пока определённые состояния в стеке.
subscribe(key, active<State>{...}) { source: Flow<I> } регистрирует
горячий поток, чьи эмиссии шлются внутрь как интенты. Жизненный цикл скоупится клаузами
active<>: рантайм диффит стек на каждом переходе, запускает поток когда
подходящее состояние есть, и отменяет когда оно ушло. Авто-переподключение с backoff (или
onError-интент) обрабатывает сбои.
На графе: подписки помечают, какие состояния «живые» и куда входят непрерывные события — состояния с активными потоками отмечаются, и рисуются повторяющиеся входящие рёбра, отличая реактивные экраны от чисто тапо-управляемых.
Эффекты (разовые выходы наружу)
Одноразовые выходы во внешний мир: навигация, снекбар, закрытие.
Эффекты реализуют MelEffect и эмитятся из списка effects перехода;
это НЕ состояние. Единственный потребитель (effect-host у MelScreen) обрабатывает их
через effects{ on<E> { effect, context -> ... } }, навигацию делает router.
Встроенные MelCloseEffect/MelExhausted закрывают экран. Эффекты
доставляются один раз (на канале), без повторов.
На графе: эффекты это рёбра, уходящие ИЗ графа — рисуются исходящие маркеры (перейти на другой экран, снекбар, закрыть), висящие на переходах, и видно, где машина экрана передаёт управление остальному приложению.
Что плагин умеет сейчас
Всё, что перечислено ниже, уже реализовано и собирается. Группы — это естественные области ответственности; они же — хорошие границы для дизайн-вариантов.
MelScreenIndex
(проектный сервис) находит Kotlin-файлы исходников через FileTypeIndex, группирует их
по пакету, где есть наследник MelMachine, и привязывает каждый файл к длиннейшему
machine-пакету-предку (чтобы контракты/оверлеи/факты из под-пакетов попадали без коллизий имён).
Для каждой группы работает платформенно-нейтральный MelGraphExtractor (в
:tools:mel-graph) поверх уже-разобранных KtFile (только
org.jetbrains.kotlin.psi.*): читает тип-аргументы MelMachine<S,I,E>,
перечисляет sealed-листья, обходит start()/graph{}/on/onAny/subscribe/include
и собирает ScreenGraph. Резолв типов — через SPI TypeResolver: в IDE это
AnalysisApiTypeResolver на K2 Analysis API с откатом на чисто-именную эвристику.
Готовый граф питает UI: правый тул-виндоу с каталогом слева и Java2D-рендером
MelGraphView справа, с живым переизвлечением по дебаунс-листенеру PSI.
Каталог и обнаружение экранов
| Возможность | Что делает |
|---|---|
| Обнаружение экранов | Сканирует все Kotlin-файлы исходников через FileTypeIndex и собирает пакеты, где есть класс с супертипом MelMachine. |
| Группировка по пакетам | Файл привязан к длиннейшему machine-пакету-предку, так что каждый экран резолвится со своими контрактами/оверлеями/фактами без коллизий имён состояний. |
| Список-каталог | JBList: имя экрана, укороченный Gradle-модуль, бейдж test, тултип с пакетом и счётчиками состояний/переходов/подписок. |
| Поиск и фильтр по модулю | SearchTextField фильтрует по имени/модулю/пакету, ComboBox сужает список до одного модуля. |
| Тоггл тест-экранов | Кнопка-переключатель прячет или показывает машины из тестовых исходников; статус сообщает, сколько спрятано. |
| Метки модуля и теста | Каждому экрану через ProjectFileIndex проставляется модуль (укороченное имя) и признак тестового исходника. |
| Кэш по modification-stamp | Графы кэшируются по счётчику модификаций PsiManager и пересчитываются только при изменении исходников. |
Отрисовка графа
| Возможность | Что делает |
|---|---|
| Слоистая Java2D-диаграмма | MelGraphView рисует граф без зависимостей (Swing/Java2D, без JCEF): BFS-ранги от старта + barycenter-упорядочивание. |
| Узлы с цветом по роли | Скруглённые узлы, цвет рамки = роль (normal/overlay/terminal/start/ghost), тело — до шести фактов плюс аннотации подписок. |
| Типизированные рёбра | Кривые стрелки по op/команде: толстое push, пунктирное бирюзовое command/async, пунктирное pop к источнику push; самопетли вынесены в инспектор. |
| Псевдо-узлы start/exit/ghost | Кольцо старта для start(), кольцо-выход для неоднозначного pop, узел «?» для статически не-резолвнутых целей next/push. |
| Сворачиваемая легенда | Кликабельная легенда поясняет цвета ролей состояний и стили линий-переходов. |
| Панель-инспектор состояния | JEditorPane: факты выбранного состояния, подписки в скоупе, действия (stay/self) и входящие/исходящие переходы с командами и эффектами. |
Взаимодействие
| Возможность | Что делает |
|---|---|
| Пан и зум | Перетаскивание мышью и зум колесом вокруг курсора, ограничение 0.3×–3.0×. |
| Контролы вида | Кнопки тулбара Fit, Zoom In/Out, Actual Size (1:1), активны при наличии графа. |
| Клик в исходник | Клик по узлу, ребру или строке инспектора открывает источник через OpenFileDescriptor по SourceRef. |
| Подсветка и выбор | Наведение подсвечивает узел и смежные рёбра (курсор-рука на кликабельном), клик выбирает состояние (синее свечение) и обновляет инспектор. |
| Авто-выбор и авто-фит | При показе графа авто-выбирается старт-состояние (иначе первое реальное), диаграмма центрируется и фитится на первой отрисовке. |
| Ручной Refresh | Кнопка сбрасывает кэш индекса и пере-сканирует проект вне EDT. |
Экспорт
| Возможность | Что делает |
|---|---|
| Экспорт PNG | Текущий граф рендерится в BufferedImage 1:1 и сохраняется через JFileChooser. |
| Копировать как Mermaid | MermaidEmitter превращает граф во flowchart (роли-classDef, стили рёбер, факты/подписки, click-директивы) в буфер обмена. |
| Эмиттер Mermaid | Генерирует Mermaid-текст с classDef по ролям, стилями push/command/pop/stay и melPost-навигацией на узлах. |
| Эмиттер JSON | JsonEmitter сериализует граф(ы) в компактный JSON (состояния, факты, рёбра, подписки) для машинной обработки. |
Живые апдейты
| Возможность | Что делает |
|---|---|
| Живой пере-скан (debounce) | PsiTreeChangeListener на физических KtFile планирует дебаунс 700 мс и лёгкий reload, чтобы каталог и граф жили вместе с правками. |
| Скан вне EDT | Сканирование на app-executor под read-action, пока список крутит спиннер и показывает статус; апдейты UI маршалятся на EDT. |
| Сохранение выбора | После пере-скана выбор восстанавливается по полному имени экрана, если он ещё существует, иначе берётся первый. |
Ядро извлечения
| Возможность | Что делает |
|---|---|
| Ядро на чистом PSI | MelGraphExtractor строит граф из уже-разобранных KtFile только на org.jetbrains.kotlin.psi.* — один код работает в IDE и headless. |
| Тип-аргументы и sealed-листья | Читает имена MelMachine<S,I,E> и перечисляет sealed-листья S в узлы с фактами (параметры первичного конструктора), полными именами и source-ref. |
| Обход клеток-переходов | Разбирает start(), graph{}, on<S,I>, onAny<I>, include(Section) в рёбра, спускаясь в ветки if/when (по ребру на ветку). |
| Резолв операции и цели | Классифицирует переход NEXT/PUSH/POP/POP_TO/STAY и резолвит цель через TypeResolver, помечая не-резолвнутые как null, а не выбрасывая. |
| Эффекты/команды/результаты | Снимает имена эффектов, наличие и ключ command(...)/commandWithProgress(...) и интенты-результаты успеха/ошибки. |
| Подписки | Разбирает subscribe(...): скоуп active<S>(), ключ-литерал (включая CommandKey/SubscriptionKey), флаг onError, испускаемые интенты (map(::X)). |
| Разбор config{debounce} | Парсит config{ debounce<I>(...) } и привязывает мс-значения к соответствующим рёбрам. |
| Оверлеи и терминалы | Помечает состояние overlay (если объявлено overlay<S> в MelScreen) и terminal (нет исходящих NEXT/PUSH). |
| Резолвер на Analysis API | AnalysisApiTypeResolver на K2 Analysis API (sealedClassInheritors, expressionType) резолвит листья и цели next/push в IDE. |
| Эвристический фолбэк | Каждый вызов Analysis API под guard-ом read-action ловит любой Throwable и откатывается на NameHeuristicResolver (same-file sealed BFS + эвристики по имени/copy-receiver). |
Что плагин мог бы уметь
Меню идей для дизайн-эксплорации — то, чего пока нет (или есть лишь частично). Статусы: в планах уже решено сделать · частично модель/данные есть, нет UI · идея кандидат на обсуждение.
Богатый инспектор состояния (вариант D)
в планахДовести нынешний базовый JEditorPane-инспектор до выбранного в дизайне варианта D:
упорядоченная палитра, разделитель на каждое поле, действия-на-месте (self-переходы с ключом
команды, debounce, интентами-результатами, эффектами), всё с кликом в исходник.
Польза: держит диаграмму чистой для плотных состояний (у Content
7 self-переходов), а детали — читаемы и навигируемы по запросу.
Маркеры в gutter на клетках
идеяИконка стейт-машины в gutter рядом с каждой on<State,Intent>{},
start(), subscribe<> и листом-состоянием; клик открывает граф,
сфокусированный на этом узле/ребре.
Польза: двусторонняя навигация код↔граф и узнавание MEL-клеток с одного взгляда.
Инспекции из detekt-набора в IDE
идеяПоказать 26 PSI-правил :tools:detekt-rules живыми инспекциями IntelliJ с
подсветкой в редакторе, а не только на сборке (правила уже чисто-PSI и работают
инкрементально).
Польза: мгновенная обратная связь по нарушениям MEL прямо при наборе.
Quick-fix к идиомам MEL
идеяQuick-fix-интенции к инспекциям: throw в reduce → эффект /ShowError,
ручной collect потока → subscribe<>, добавить пропущенный onError,
сырой конструктор фреймворка → MEL-фабрика, is-проверка → клетка on<>.
Польза: каждое нарушение лечится одним нажатием — дешевле писать идиоматичный MEL и переносить с v3.
Вкладка миграции MVI v3 → MEL
в планахОтдельная вкладка тул-виндоу: находит конструкции v3 (Mutator/хендлеры, Listen-эффекты,
type-checks) и предлагает MEL-маппинг (хендлеры → on<>, Listen-эффект →
команда/подписка, переформовка sealed), повторяя примеры из mel-migration.
Польза: ведёт по конкретным повторяющимся шагам переноса легаси-экранов — главный барьер внедрения.
Structure View машины
идеяДерево/outline выбранной машины: состояния (с бейджами start/overlay/terminal и фактами) →
их клетки on<> → эффекты/команды/результаты, плюс ветка подписок. На той же
модели MelGraphExtractor.
Польза: быстрый текстовый индекс машины для навигации и ревью — дополнение к графу для тех, кто мыслит деревьями.
Интерактивный симулятор / степпер
идеяЗадать стартовое состояние и кликать интенты: граф подсвечивает активное состояние, анимирует пройденное ребро, показывает факты результата, эффекты в очереди и интенты-результаты команд. Сухой прогон на уровне модели, без исполнения.
Польза: рассуждать о достижимости и потоке («что будет, если послать Rescan из Content?») без сборки и тапов по живому экрану.
Слой эффектов / команд / подписок
идеяВторой режим диаграммы: команды (ключ, debounce, интенты-результаты успеха/ошибки) и подписки (ключ, скоуп-состояния, испускаемые интенты, onError) как узлы, связанные с состояниями, которые их «взводят», и интентами, что они подают обратно.
Польза: делает невидимый async-слой явным — видно, какие состояния взводят какие команды/подписки и где циклы обратной связи.
Git-дифф графа экрана
идеяСравнить граф экрана между двумя git-ревизиями (рабочее дерево vs HEAD или любые два коммита) и визуально диффнуть: добавленные/удалённые состояния и рёбра, изменения ключей команд, debounce, эффектов, скоупа подписок — с цветовой разметкой.
Польза: показывает поведенческие изменения машины в терминах ревьюера, а не стеной Kotlin-диффа.
Find-usages контракт↔машина↔view
идеяРасширить Find Usages: от интента или состояния прыгать туда, где он объявлен в контракте, где
обработан (on<>) или порождён (результат команды / эмит подписки) и где
отрисован/диспатчится в Compose-view.
Польза: объединяет три слоя MEL под одним жестом — трассировать интент end-to-end без ручного текст-поиска по файлам.
Мастер нового экрана
идеяДействие New → MEL Screen: генерит контракт (sealed State/Intent/Effect), скелет машины
(start(), пара on<>, заглушка subscribe<>) и
Compose-view + проводку melViewModel, с пакетом и именами из целевого модуля.
Польза: убирает бойлерплейт и зашивает канонический layout файлов — новые экраны идиоматичны by construction.
Live-templates / postfix MEL
идеяШаблоны редактора для частых клеток: on<S,I>{ next/push/pop/stay },
start(), subscribe<>(key){} с onError, блок команды с
результатами, лист-состояние с фактами; postfix-варианты на имени состояния.
Польза: ускоряет ручное письмо машин и навязывает правильную форму конструкций (например всегда предлагает слот onError).
detekt-находки поверх графа
идеяБейджи на узлах и рёбрах диаграммы по MEL-находкам detekt в их исходном диапазоне (например значок-предупреждение на клетке, у которой reduce с побочками), с кликом-в-фикс.
Польза: связывает архитектурный линт с визуальной моделью — проблемы видны ровно там, где живёт нарушающее поведение.
Экспорт графа в docs-сайт
частичноОдно действие — отдать диаграмму экрана (Mermaid или PNG, плагин уже умеет) прямо в формат
docs/-сайта MEL (например в mel-migration.html или пер-экранную
страницу), а не копировать руками.
Польза: держит опубликованную документацию MEL в синхроне с кодом без ручного сопровождения диаграмм.
Minimap и focus-режим
идеяMinimap-обзор для навигации по большим машинам и focus-режим, гасящий всё кроме выбранного узла и его 1-hop-окрестности (входящие/исходящие рёбра, взведённые команды, скоуп-подписки).
Польза: большие много-состоянийные экраны остаются навигируемыми, можно сосредоточиться на окрестности одного состояния без потери общего контекста.
Линты покрытия / мёртвых интентов
идеяСтатически флагать интенты из контракта, которые не обрабатывает ни одна on<>;
состояния, недостижимые ни одним ребром; интенты-результаты команд, которые никто не
обрабатывает — как бейджи на графе и как предупреждения.
Польза: ловит неполные/устаревшие машины (сироты-интенты, недостижимые состояния), которые компилируются, но это мёртвое или отсутствующее поведение.
Подсветка не-резолвнутых целей
частичноЭкстрактор уже метит рёбра, чью цель нельзя резолвить статически (next(EXPR) со
сложным выражением), как to=null. Рисовать их пунктирными «висячими» рёбрами и
подсказывать сделать цель статически анализируемой.
Польза: делает границы статического анализа видимыми и подталкивает код к формам, понятным графу (и ревьюеру).
Оверлей по секциям / include
частичноМодель уже хранит, из какой секции (include(...)) пришла клетка on<>
или подписка. Красить/группировать узлы и рёбра по секции-источнику и дать фильтр «показать одну
секцию».
Польза: помогает понять, как машина собрана из переиспользуемых секций, и изолировать свою часть графа.
Технологии: отрисовка и UI
На чём всё нарисовано сегодня и какие есть альтернативы — с вердиктом по каждой. Вердикты: рекомендуется · годится · мимо.
sinceBuild=261),
Kotlin, K2 Analysis API, JDK 21. JCEF в этой JBR недоступен (нет libcef.so)
→ браузерный рендер (Mermaid.js / D3 / Cytoscape.js / dagre-in-JS) из коробки не работает. Ни
graphviz dot, ни mmdc не установлены → нативные CLI-рендереры отпадают.
ELK есть только транзитивно через android-плагин и тащит EMF + нестабильную зависимость.
Модуль не объявляет graph-библиотек: компилит нейтральное ядро mel-graph и работает на
собственном PSI IDE. Требование к любому варианту: работает из коробки, без нативных
зависимостей, без JCEF, темится light/dark через JBColor, совместим с K2/261.
Слой 1 — Layout (раскладка узлов и рёбер)
Свой слоистый layout (текущий)
рекомендуетсяСамописный Sugiyama-конвейер в MelGraphView.computeLayout(): BFS-ранги
от синтетического старта по прямым рёбрам (next/push), 4 прохода barycenter, центрирование по
слоям. Чистый Kotlin, ноль зависимостей.
Вердикт: единственный, кто уже проверен, без зависимостей и совпадает с таксономией узлов MEL из коробки.
ELK (Eclipse Layout Kernel)
годитсяЗрелая JVM-библиотека; алгоритм layered — полноценный Sugiyama с
рангами, упорядочиванием, портами и ортогональным роутингом. Присутствует транзитивно через
android-плагин.
Вердикт: отличный вывод, но EMF + хрупкая транзитивность нарушают «чисто из коробки».
Graphviz / dot
мимоКлассический внешний движок раскладки; эмитим DOT и зовём бинарь dot за
координатами.
dot не установлен → не работает из коробки; шелл к
нативному бинарю — ровно та нативная зависимость, которой избегаем.Вердикт: требует отсутствующий нативный бинарь.
JGraphX / mxGraph layouts
мимоSwing-библиотека графов с алгоритмами раскладки (hierarchical, organic, tree), применимыми отдельно от рендера.
Вердикт: необслуживаемая зависимость, дублирует наш самописный layout.
dagre (JS)
мимоПопулярная JS-раскладка ориентированных графов (движок за flowchart-ами Mermaid); работала бы в JS-контексте внутри IDE.
Вердикт: зависит от JCEF/JS-рантайма, которого здесь нет.
Sugiyama своими руками (v2)
годитсяЭволюция текущего: виртуальные узлы для длинных рёбер, median + transpose для упорядочивания, координатный проход (Brandes–Köpf) для прямых рёбер.
Вердикт: естественный путь апгрейда без зависимостей, если качество текущей раскладки станет узким местом.
Слой 2 — Холст (рендер)
Java2D / Swing своё (текущее)
рекомендуетсяMelGraphView — это JPanel, рисующий всё в
paintComponent через Graphics2D: скруглённые узлы по ролям с
фактами/подписками, безье-рёбра с наконечниками и подписями, пунктиры для command/pop,
сворачиваемая легенда, зум колесом, пан, hit-test узлов/рёбер, экспорт PNG.
Вердикт: единственный рендер, который уже работает из коробки без нативных зависимостей и с полной темизацией JBColor.
JCEF + Mermaid / D3
мимоВстроить Chromium (JBCefBrowser) в тул-виндоу и рисовать граф как
HTML/SVG через Mermaid.js или D3, с мостом кликов обратно в IDE.
libcef.so) —
просто не загрузится; потребовал бы подмены JBR на CEF-сборку — вне контроля плагина; тяжелее по
памяти, мост в исходник сложнее, тему синхронизировать руками.Вердикт: libcef отсутствует в целевом рантайме — вообще не отрисует из коробки.
JGraphX Swing-компонент
мимоmxGraphComponent (Swing-холст JGraphX) для рендера и взаимодействия, в
паре с его hierarchical-раскладкой.
ScreenGraph; меняет рабочий
рендер на легаси-зависимость.Вердикт: заброшенная библиотека и не-родная тема ради возможностей, что уже есть.
Compose for Desktop / Jewel
мимоРисовать холст через Canvas/Modifier Compose Multiplatform, с мостом Jewel, чтобы Compose-UI жил внутри Swing-тул-виндоу с родной IDE-темой.
Вердикт: Skiko везёт нативный Skia, а цена бандла/зрелости перевешивает выигрыш над рабочим Java2D.
SVG-рендер (Batik / SVG IDE)
годитсяГенерить SVG-документ графа и показывать Swing-вьювером (Apache Batik или
SVG/иконочный загрузчик платформы) вместо прямого рисования через Graphics2D.
Вердикт: хорош как дополнительный путь экспорта, но плохой основной интерактивный холст против текущего Java2D.
Слой 3 — UI-обвязка (панели, список, тулбар)
IntelliJ UI DSL + JBComponents (текущее)
рекомендуетсяРабочая обвязка: ToolWindowFactory создаёт JBSplitter
(каталог | граф), JBList с ColoredListCellRenderer,
SearchTextField + ComboBox, ActionToolbar
(Refresh/тесты/fit/zoom/1:1/export/copy), JEditorPane-инспектор и статус-JLabel
— всё темится платформой.
setPaintBusy, экшены тулбара, DumbAware, HiDPI, light/dark); ноль зависимостей;
корректная EDT/read-action-проводка уже есть.JEditorPane
ограничен (ручной CSS, базовый HTML); UI DSL многословен для сложных форм.Вердикт: first-class IDE-обвязка, уже собрана, темизирована и без зависимостей.
Чистый Swing
мимоСобрать обвязку из голых javax.swing (JList,
JSplitPane, JTextField, JToolBar) без JB*-обёрток.
Вердикт: выбрасывает бесплатную тему/поведение, которые уже даёт текущая JB-обвязка.
Compose / Jewel
мимоРеализовать панели, список, инспектор и тулбар декларативно на Compose for Desktop через тему-мост Jewel.
Вердикт: тяжёлый рантайм (вкл. нативный Skiko) и риск зрелости ради обвязки, что платформа уже даёт.
MelGraphView — из коробки, без
нативщины, темится JBColor, уже умеет пан/зум/hit-test/PNG и клик-в-исходник. Обвязка:
IntelliJ UI DSL + JBComponents за родной вид, тему, HiDPI и поведение при нулевой цене.
Браузерный путь (JCEF) отклонён — libcef.so отсутствует; Graphviz/dot — нативный
бинарь не установлен; ELK — сильнейшая чисто-JVM альтернатива раскладки, но лишь «годится» из-за
транзитивности и EMF; Compose/Jewel и Skiko — нативный Skia и цена бандла. Единственный устаревший
артефакт — документация: build.gradle.kts и plugin.xml всё ещё
говорят «rendered via JCEF», хотя реально едет Java2D-рендер без зависимостей.
Что нужно от дизайна
По этому брифу в проекте Claude Design собираются визуальные варианты интерфейса плагина. Поверхности, по которым стоит дать варианты:
- Общий каркас тул-виндоу — как уживаются каталог ↔ граф ↔ инспектор; где фильтры, тулбар, статус; как встают будущие вкладки (миграция, слой эффектов, симулятор) — табы, сплиты или режимы.
- Холст графа — оформление узла (факты, подписки, роль), презентация действий-на-месте (self-переходов), стили рёбер по операции, псевдо-узлы start/exit/ghost, легенда.
- Инспектор состояния — вариант D уже выбран; довести оформление: палитра «1 цвет = 1 смысл», разделители полей, читаемые шрифты, действия с деталями (команда, debounce, результаты, эффекты).
- Поверхности будущих фич — как показывать инспекции/quick-fix, git-дифф графа, minimap/focus, бейджи detekt на графе, степпер-симулятор.
- Палитра и легенда — единая система цветов ролей и стилей рёбер для тёмной и светлой темы.
tools/mel-idea-plugin/design/ (варианты 0 / A / C / D
презентации действий + node-anatomy.html; выбран D) и
design/spec.md. Их палитру, легенду и оформление узла переносить как базу. Реальный
референс данных — экран QrSign (состояния Loading / Content / Stub / Confirm; у
Content 7 self-переходов — стресс-тест плотности).
Источник истины по поведению — код в :tools:mel-idea-plugin и :tools:mel-graph;
по архитектуре MEL — :core:mel, затем спека. Если документ
разойдётся с кодом — прав код.