| 1 | # ADR 0055: Skia instrument composition pipeline (Intent -> Declutter -> Layout -> Render) |
| 2 | |
| 3 | **Статус:** Accepted |
| 4 | **Дата:** 2026-04-17 |
| 5 | |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR | Роль | |
| 9 | |-----|------| |
| 10 | | [0036](0036-cds-channel-compositor-surface-pipeline.md) | Канал → CDS → surface | |
| 11 | | [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md) | Instrument / slot | |
| 12 | | [0049](0049-skia-surface-rollout-over-avalonia-host.md) | Поэтапный Skia rollout | |
| 13 | | [0053](0053-semantic-map-control-flow-pfd.md) | Semantic Map, control flow | |
| 14 | | [0056](0056-semantic-map-pipeline-adoption.md) | Первый consumer — карта намерений | |
| 15 | | [0064](0064-deck-primitives-visual-language-render-layer-and-palette.md) | Стадия Render, примитивы deck | |
| 16 | |
| 17 | ## Резюме |
| 18 | |
| 19 | - Общий pipeline Skia-инструментов: **Intent → Declutter → Layout → Render**. |
| 20 | - Единые invariants для semantic map, deck и будущих приборов. |
| 21 | - Потребители — [0056](0056-semantic-map-pipeline-adoption.md), [0057](0057-chat-surface-pipeline-adoption.md). |
| 22 | |
| 23 | |
| 24 | --- |
| 25 | ## Контекст |
| 26 | |
| 27 | Skia-инструменты в CIDE (`workspace_navigation_map`, будущие приборы и overlays) размещаются через host surface compositor, но внутренняя композиция каждого инструмента (данные, declutter, геометрия, рендер-правила) исторически смешивалась в конкретной фиче. |
| 28 | |
| 29 | Это дало два системных эффекта: |
| 30 | |
| 31 | 1. Граница ответственности размыта: "surface" и "instrument internals" смешиваются в обсуждении и коде. |
| 32 | 2. Каждая новая Skia-фича рискует заново изобретать собственный мини-pipeline без общих invariants. |
| 33 | 3. На примере `controlFlow` по [0053](0053-semantic-map-control-flow-pfd.md) видно, что без отдельного declutter-слоя читаемость деградирует на маленьком viewport. |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## Решение |
| 38 | |
| 39 | <a id="adr0055-p1"></a> |
| 40 | ### 1) Ввести единый pipeline для всех Skia-инструментов |
| 41 | |
| 42 | Для каждого Skia-инструмента фиксируется внутренняя последовательность: |
| 43 | |
| 44 | 1. **Intent**: построение доменной модели состояния инструмента (source-of-truth для содержимого). |
| 45 | 2. **Declutter**: policy-фильтрация/агрегация и приоритизация перед геометрией. |
| 46 | 3. **Layout**: геометрическая раскладка. |
| 47 | 4. **Render**: отрисовка готовой scene без бизнес-логики. |
| 48 | |
| 49 | Surface compositor при этом остаётся внешним слоем и отвечает только за "куда инструмент монтируется" (slot/surface), но не за внутреннюю геометрию/declutter инструмента. |
| 50 | |
| 51 | <a id="adr0055-p2"></a> |
| 52 | ### 2) Внутренний композитор инструмента обязателен |
| 53 | |
| 54 | В каждом инструменте вводится отдельный композитор (например `*Compositor`), который: |
| 55 | |
| 56 | - выбирает layout-движок по доменному уровню детализации; |
| 57 | - применяет policy высоты/плотности viewport; |
| 58 | - возвращает готовую scene и параметры отображения. |
| 59 | |
| 60 | Это канонический путь для новых Skia-фич, чтобы не дублировать логику в VM и контролах. |
| 61 | |
| 62 | <a id="adr0055-p3"></a> |
| 63 | ### 3) Declutter — общий слой политики, не "if'ы" в layout |
| 64 | |
| 65 | Правила снижения шума (плотность, агрегация повторов, приоритет главного сценария над вторичными деталями — у графа: main-flow vs второстепенные рёбра) вводятся как отдельный policy-слой с единым интерфейсом для разных инструментов. |
| 66 | Layout не должен решать "что скрывать", а только "как раскладывать то, что уже отобрано". |
| 67 | |
| 68 | <a id="adr0055-p4"></a> |
| 69 | ### 4) Layout / Render: инварианты читаемости (ориентиры HF / кабина) |
| 70 | |
| 71 | Ниже — **инструмент-агностичные** правила: они относятся к любому Skia-инструменту с pipeline из §1 (таймлайн чата, шкала, граф потока управления, схема и т.д.). Примеры в скобках иллюстрируют частный случай; каноническая детализация для control-flow — [0053](0053-semantic-map-control-flow-pfd.md). Связь с моделью внимания кокпита: [0021](0021-pfd-mfd-cockpit-attention-model.md), [0046](0046-presentation-layout-authority-and-cockpit-invariants.md). |
| 72 | |
| 73 | 1. **Иерархия внимания и порядок отрисовки (Render).** У инструмента задаётся **фиксированный порядок слоёв** базовой сцены: сначала фон/сетка и «несущие» примитивы, затем основной контент, затем подписи и вспомогательная разметка в пределах своих зон. Сигналы внимания из CDS/каналов ([0036](0036-cds-channel-compositor-surface-pipeline.md)) — подсветки, трассы, health — рисуются **поверх** этой базы, без перестройки доменной геометрии под «алерт». Интерактивный фокус (курсор, выделение) — верхний читаемый слой относительно статики. *(Пример: граф — рёбра → узлы → подписи в полосе; чат — фон ветки → пузыри → метки времени.)* |
| 74 | |
| 75 | 2. **Бюджет шума: Declutter выбирает *что*, Layout ограничивает *сколько помещается*.** Declutter задаёт набор видимых сущностей и агрегацию; Layout навешивает **плотностные и геометрические потолки** под viewport (минимальные зазоры между блоками, лимиты строк/колонок, резервы под вспомогательные колонки). Нельзя компенсировать перегруз контентом одним масштабом, если policy уже сигнализирует «слишком много» — сначала снизить объём или уровень детализации. *(Пример: граф — шаг по вертикали и ширина полосы; чат — число видимых сообщений и высота превью.)* |
| 76 | |
| 77 | 3. **Уровни детализации (Glance / Normal / Inspect) — прежде всего Declutter, не только геометрия.** Доменный уровень детализации (в т.ч. общие `glance` / `normal` / `inspect` из плана внедрения) задаёт **отбор и агрегацию до Layout**. Layout меняет метрики раскладки, но не подменяет решение «сколько сущностей показывать» между Glance и Inspect. *(Пример: `CodeNavigationMapDetailLevel` для карты; для другого инструмента — свой enum/шкала, тот же принцип разделения ролей.)* |
| 78 | |
| 79 | 4. **Минимумы читаемости в Glance (Render / тема).** При сжатии viewport сохранять **нижние пороги** читаемости: размер глифа, толщина линий, контраст обводок — чтобы режим «взгляд сбоку» не превращался в неразличимый шум. Числа задаются темой/константами инструмента; инвариант — **не жертвовать различимостью ради влезания в rect**. *(Пример: минимальная толщина связей и обводок узлов; минимальная высота строки в списке.)* |
| 80 | |
| 81 | 5. **Стабильные пространственные роли (Layout).** Подписи, легенды, шкалы и прочая вспомогательная графика **привязываются к якорной зоне основного контента** (смежная область, фиксированный отступ от «полосы» данных), а не прыгают в произвольный угол viewport — ниже стоимость сопоставления метки с объектом. *(Пример: легенда у полосы графа [0053](0053-semantic-map-control-flow-pfd.md); подпись оси у края той же области, что и данные.)* |
| 82 | |
| 83 | 6. **Алертинг поверх базы.** Композиторы каналов (TraceFlow, health и т.д.) добавляют **overlay** к уже собранной базовой scene инструмента: они не дублируют Intent/Layout доменной модели, а только отображают состояние внимания/трасс/статуса поверх неё — для любого типа базового прибора. |
| 84 | |
| 85 | **Контрактные проверки (тесты pipeline):** на малом viewport — нет **запрещённых для инструмента** пересечений layout-примитивов (коллизии, нечитаемое наложение) при заявленном уровне детализации; при переходе к более глубокому уровню (например Glance → Inspect) объём полезной информации **не сужается** без явной policy-деградации (пустое состояние, ошибка данных). Конкретика «что считать коллизией» — в контракте каждого инструмента. |
| 86 | |
| 87 | --- |
| 88 | |
| 89 | ## Последствия |
| 90 | |
| 91 | ### Плюсы |
| 92 | |
| 93 | - Чёткая архитектурная граница: host surface vs instrument internals. |
| 94 | - Единый контракт для всех будущих Skia-фич и приборов. |
| 95 | - Можно независимо эволюционировать declutter/layout/render без регрессий в slot-композиции. |
| 96 | |
| 97 | ### Минусы |
| 98 | |
| 99 | - Больше сущностей и контрактов в слое Skia-инструментов. |
| 100 | - Нужны отдельные тесты pipeline-уровня (не только layout unit tests). |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## Не-цели |
| 105 | |
| 106 | - Не менять ADR [0036](0036-cds-channel-compositor-surface-pipeline.md) и [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md): внешняя surface-композиция остаётся как есть. |
| 107 | - Не фиксировать здесь конечный UI-стиль всех edge-кейсов и всех инструментов (это эволюция в рамках pipeline и policy). |
| 108 | |
| 109 | --- |
| 110 | |
| 111 | ## План внедрения (минимум) |
| 112 | |
| 113 | 1. Зафиксировать базовый контракт общего pipeline для Skia-инструментов. |
| 114 | 2. Канонизировать `CodeNavigationMapCompositor` как первый внедрённый адаптер этого контракта. |
| 115 | 3. Выделить общий `DeclutterPolicy` интерфейс (минимум: `glance`, `normal`, `inspect`) и инструмент-специфичные реализации. |
| 116 | 4. Зафиксировать контрактные тесты pipeline: |
| 117 | - стабильная композиция scene при одинаковом входе; |
| 118 | - читаемость на малом viewport (без критических коллизий/наложений по контракту инструмента); |
| 119 | - корректная деградация при неполных входных данных (без ложного контента). |
| 120 | |
| 121 | |