Дизайн-бриф

Плагин MEL для Android Studio

Тул-виндоу, который показывает MEL-экраны как стейт-машины: каталог слева, интерактивная диаграмма состояний справа, прыжок в код по клику. Этот документ — вход для дизайн-эксплорации: что плагин уже умеет, что мог бы уметь, на чём всё нарисовано.

О документе

Помимо ядра (:core:mel) и detekt-набора (:tools:detekt-rules) у MEL есть IDE-плагин — модуль :tools:mel-idea-plugin на платформе IntelliJ. Он уже собирается и работает: рисует граф экрана своим Java2D-рендерером (без JCEF) и навигирует в исходник. Дальше его хочется развивать — и сперва спроектировать визуально.

Документ написан, чтобы лечь в проект Claude Design, где по нему будут собираться варианты дизайна интерфейса плагина. Поэтому он самодостаточен: дизайнеру, который никогда не видел MEL, его хватит, чтобы понять предметную область, текущие возможности и технические рамки.

Что внутри (2) ключевые идеи MEL — словарь предметной области и почему экран рисуется графом. (3) полный список того, что плагин умеет сегодня, по областям. (4) меню фич, которые он мог бы уметь, со статусом. (5) на чём рисовать и строить UI — варианты с «за/против» и жёсткими ограничениями. (6) чего, собственно, ждём от дизайна.

Источник истины по архитектуре 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 закрывают экран. Эффекты доставляются один раз (на канале), без повторов.

На графе: эффекты это рёбра, уходящие ИЗ графа — рисуются исходящие маркеры (перейти на другой экран, снекбар, закрыть), висящие на переходах, и видно, где машина экрана передаёт управление остальному приложению.

Что плагин умеет сейчас

Всё, что перечислено ниже, уже реализовано и собирается. Группы — это естественные области ответственности; они же — хорошие границы для дизайн-вариантов.

Как это устроено (одним абзацем) Плагин извлекает MEL-машины статически, без компиляции. 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.
Копировать как MermaidMermaidEmitter превращает граф во flowchart (роли-classDef, стили рёбер, факты/подписки, click-директивы) в буфер обмена.
Эмиттер MermaidГенерирует Mermaid-текст с classDef по ролям, стилями push/command/pop/stay и melPost-навигацией на узлах.
Эмиттер JSONJsonEmitter сериализует граф(ы) в компактный JSON (состояния, факты, рёбра, подписки) для машинной обработки.

Живые апдейты

ВозможностьЧто делает
Живой пере-скан (debounce)PsiTreeChangeListener на физических KtFile планирует дебаунс 700 мс и лёгкий reload, чтобы каталог и граф жили вместе с правками.
Скан вне EDTСканирование на app-executor под read-action, пока список крутит спиннер и показывает статус; апдейты UI маршалятся на EDT.
Сохранение выбораПосле пере-скана выбор восстанавливается по полному имени экрана, если он ещё существует, иначе берётся первый.

Ядро извлечения

ВозможностьЧто делает
Ядро на чистом PSIMelGraphExtractor строит граф из уже-разобранных 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 APIAnalysisApiTypeResolver на 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

На чём всё нарисовано сегодня и какие есть альтернативы — с вердиктом по каждой. Вердикты: рекомендуется · годится · мимо.

Жёсткие ограничения Цель — IntelliJ Platform 2026.1 / Android Studio Quail (build 261, 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 (старт-кольцо, push/pop/stay, ghost/terminal); cycle-safe; полностью под контролем.
Против: нет роутинга рёбер (безье «на глаз»), нет портов и развода самопетель/коллизий меток; barycenter — эвристика, широкие графы всё равно пересекаются.

Вердикт: единственный, кто уже проверен, без зависимостей и совпадает с таксономией узлов MEL из коробки.

ELK (Eclipse Layout Kernel)

годится

Зрелая JVM-библиотека; алгоритм layered — полноценный Sugiyama с рангами, упорядочиванием, портами и ортогональным роутингом. Присутствует транзитивно через android-плагин.

За: лучшее качество layered/orthogonal; настоящий роутинг и порты, которых нам не хватает; чистый JVM; зримо улучшит плотные графы.
Против: бандл — транзитивный (хрупко между 261.x и по class-loader-скоупу); тащит EMF и нестабильную зависимость; как прямая зависимость раздувает плагин и рискует конфликтом версий; API многословный.

Вердикт: отличный вывод, но EMF + хрупкая транзитивность нарушают «чисто из коробки».

Graphviz / dot

мимо

Классический внешний движок раскладки; эмитим DOT и зовём бинарь dot за координатами.

За: отличное качество раскладки и роутинга; DOT легко эмитить (Mermaid уже умеем).
Против: dot не установлен → не работает из коробки; шелл к нативному бинарю — ровно та нативная зависимость, которой избегаем.

Вердикт: требует отсутствующий нативный бинарь.

JGraphX / mxGraph layouts

мимо

Swing-библиотека графов с алгоритмами раскладки (hierarchical, organic, tree), применимыми отдельно от рендера.

За: чистая Java/Swing, без нативного кода и JCEF; hierarchical — разумный Sugiyama.
Против: JGraphX заброшен/EOL; крупная зависимость только ради раскладки; модель завязана на mxGraph-ячейки, нужен адаптер туда-обратно; темизация мимо JBColor.

Вердикт: необслуживаемая зависимость, дублирует наш самописный layout.

dagre (JS)

мимо

Популярная JS-раскладка ориентированных графов (движок за flowchart-ами Mermaid); работала бы в JS-контексте внутри IDE.

За: хорошее качество layered; известный; тот же движок, что подразумевает Mermaid-экспорт.
Против: нужен JS-рантайм — в IDE это JCEF, которого нет; встраивать отдельный JS-движок (Nashorn убран в JDK 21, GraalJS большой) ради раскладки абсурдно.

Вердикт: зависит от JCEF/JS-рантайма, которого здесь нет.

Sugiyama своими руками (v2)

годится

Эволюция текущего: виртуальные узлы для длинных рёбер, median + transpose для упорядочивания, координатный проход (Brandes–Köpf) для прямых рёбер.

За: остаётся без зависимостей, чистый Kotlin; чинит два слабых места текущей раскладки; инкрементально — переиспользует ранги/barycenter; полный контроль над видами узлов MEL.
Против: реальная инженерия и класс тонких багов (виртуальные узлы, разрыв циклов), которые ELK уже решил; риск переизобрести ELK плохо.

Вердикт: естественный путь апгрейда без зависимостей, если качество текущей раскладки станет узким местом.

Слой 2 — Холст (рендер)

Java2D / Swing своё (текущее)

рекомендуется

MelGraphView — это JPanel, рисующий всё в paintComponent через Graphics2D: скруглённые узлы по ролям с фактами/подписками, безье-рёбра с наконечниками и подписями, пунктиры для command/pop, сворачиваемая легенда, зум колесом, пан, hit-test узлов/рёбер, экспорт PNG.

За: ноль зависимостей, без нативщины и JCEF; полный контроль вида + темы через JBColor + HiDPI; клик-в-исходник по PSI-офсетам; дешёвый экспорт PNG; родная интеграция с EDT и тул-виндоу.
Против: весь код рисования/взаимодействия пишется руками; нет scene-graph (анимации, частичная перерисовка, rich-text — вручную); большой граф перерисовывает всю панель.

Вердикт: единственный рендер, который уже работает из коробки без нативных зависимостей и с полной темизацией JBColor.

JCEF + Mermaid / D3

мимо

Встроить Chromium (JBCefBrowser) в тул-виндоу и рисовать граф как HTML/SVG через Mermaid.js или D3, с мостом кликов обратно в IDE.

За: богатый привычный веб-рендер; Mermaid уже эмитим; CSS-темизация и анимации даром; большая веб-экосистема.
Против: JCEF недоступен в этой JBR (нет libcef.so) — просто не загрузится; потребовал бы подмены JBR на CEF-сборку — вне контроля плагина; тяжелее по памяти, мост в исходник сложнее, тему синхронизировать руками.

Вердикт: libcef отсутствует в целевом рантайме — вообще не отрисует из коробки.

JGraphX Swing-компонент

мимо

mxGraphComponent (Swing-холст JGraphX) для рендера и взаимодействия, в паре с его hierarchical-раскладкой.

За: встроенные пан/зум/выбор/роутинг и настоящая модель ячеек — меньше ручного кода; чистый Swing, без нативщины/JCEF.
Против: библиотека заброшена/EOL; стили не следуют JBColor (тема и HiDPI — тяжёлая кастомизация); модель ячеек чужда нашему ScreenGraph; меняет рабочий рендер на легаси-зависимость.

Вердикт: заброшенная библиотека и не-родная тема ради возможностей, что уже есть.

Compose for Desktop / Jewel

мимо

Рисовать холст через Canvas/Modifier Compose Multiplatform, с мостом Jewel, чтобы Compose-UI жил внутри Swing-тул-виндоу с родной IDE-темой.

За: современный декларативный рендер и жесты; Jewel мапит IDE-тему (light/dark); общие примитивы с Compose-командой.
Против: тащит Compose/Skiko + Jewel (крупно, и Skiko несёт нативный Skia — против ограничения); зрелость Jewel-в-плагине на 261 ещё растёт; крупный переписыв рабочего Java2D без функционального выигрыша.

Вердикт: Skiko везёт нативный Skia, а цена бандла/зрелости перевешивает выигрыш над рабочим Java2D.

SVG-рендер (Batik / SVG IDE)

годится

Генерить SVG-документ графа и показывать Swing-вьювером (Apache Batik или SVG/иконочный загрузчик платформы) вместо прямого рисования через Graphics2D.

За: декларативный вывод, независимый от разрешения; SVG заодно — чистый формат экспорта; можно переиспользовать уже посчитанные координаты; без JCEF.
Против: Batik большой, его интерактивность (hit-test, hover, клик-в-исходник) неуклюжа против прямого Java2D; тема = регенерация SVG на смену темы; загрузчик платформы заточен под статические иконки.

Вердикт: хорош как дополнительный путь экспорта, но плохой основной интерактивный холст против текущего Java2D.

Слой 3 — UI-обвязка (панели, список, тулбар)

IntelliJ UI DSL + JBComponents (текущее)

рекомендуется

Рабочая обвязка: ToolWindowFactory создаёт JBSplitter (каталог | граф), JBList с ColoredListCellRenderer, SearchTextField + ComboBox, ActionToolbar (Refresh/тесты/fit/zoom/1:1/export/copy), JEditorPane-инспектор и статус-JLabel — всё темится платформой.

За: родной IDE-вид и поведение даром (поиск, спиннер setPaintBusy, экшены тулбара, DumbAware, HiDPI, light/dark); ноль зависимостей; корректная EDT/read-action-проводка уже есть.
Против: Swing-бойлерплейт; HTML-инспектор на JEditorPane ограничен (ручной CSS, базовый HTML); UI DSL многословен для сложных форм.

Вердикт: first-class IDE-обвязка, уже собрана, темизирована и без зависимостей.

Чистый Swing

мимо

Собрать обвязку из голых javax.swing (JList, JSplitPane, JTextField, JToolBar) без JB*-обёрток.

За: нечего учить из API платформы; максимально портативный Swing.
Против: теряет тему IDE, HiDPI, спиннеры, speed-search и семантику тулбара, что дают JBComponents; всё это переписываешь сам и выглядит чужеродно.

Вердикт: выбрасывает бесплатную тему/поведение, которые уже даёт текущая JB-обвязка.

Compose / Jewel

мимо

Реализовать панели, список, инспектор и тулбар декларативно на Compose for Desktop через тему-мост Jewel.

За: декларативный реактивный UI; Jewel темит под IDE; приятно для инспектора (богатый layout вместо HTML-в-JEditorPane).
Против: тащит Compose/Skiko + Jewel (крупно, у Skiko нативный компонент) ради обвязки, что JBComponents уже делают; зрелость на 261 растёт; трение интеропа с ActionToolbar/ToolWindow и существующей Swing-панелью графа.

Вердикт: тяжёлый рантайм (вкл. нативный Skiko) и риск зрелости ради обвязки, что платформа уже даёт.

Итоговая рекомендация Оставить и точечно докручивать текущий стек Swing + Java2D — это единственная комбинация, удовлетворяющая всем жёстким ограничениям сегодня. Layout: свой слоистый (Sugiyama-style) layout, с ручным Sugiyama v2 как путём апгрейда без зависимостей, если качество станет узким местом. Рендер: Java2D/Swing-холст 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 на графе, степпер-симулятор.
  • Палитра и легенда — единая система цветов ролей и стилей рёбер для тёмной и светлой темы.
Рамка реализуемости Любой вариант должен ложиться в Swing/Java2D + JBColor, две темы (light/dark), без JCEF и нативных зависимостей (см. раздел 5). Анимации и rich-text возможны, но пишутся руками поверх Java2D.
Что уже есть в репозитории Макеты узла-уровня лежат в 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, затем спека. Если документ разойдётся с кодом — прав код.