| 1 | # ADR 0050: Декларативная карта «инструмент → зона/слот» в TOML |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-04-16 |
| 5 | |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR / документ | Роль | |
| 9 | |----------------|------| |
| 10 | | [0017](0017-multi-window-workspace-and-agent-surfaces.md) | `presentation` — топология дисплеев, не слоты | |
| 11 | | [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md) | `instrument_id` + `slot_id` | |
| 12 | | [0028](0028-user-settings-toml-localappdata-and-secrets.md) | Личный `settings.toml` | |
| 13 | | [0010](0010-ui-modes-toml-configuration.md) | UiModes, бандлы | |
| 14 | | [0036](0036-cds-channel-compositor-surface-pipeline.md) | CDS, инструменты на поверхности | |
| 15 | | [`cds-contract-v0.md`](../design/cds-contract-v0.md) | Чертёж CDS | |
| 16 | | [`[code_navigation]` в samples](../samples/settings.toml) | Аналогия слоёв: bundle + repo + user | |
| 17 | |
| 18 | ## Резюме |
| 19 | |
| 20 | - Декларативная карта **инструмент → зона/слот** в TOML. |
| 21 | - Merge bundle/repo/user; `InstrumentPlacementRuntime` + compositor. |
| 22 | |
| 23 | |
| 24 | ### Снимок реализации |
| 25 | |
| 26 | | Элемент | Значение | |
| 27 | |---------|----------| |
| 28 | | TOML | `[instrument_routing]`, merge bundle/repo/user | |
| 29 | | Runtime | `InstrumentPlacementRuntime`, `MainWindowHostSurfaceCompositor` | |
| 30 | |
| 31 | --- |
| 32 | ## Контекст |
| 33 | |
| 34 | Сегодня **где какой инструмент кабины** (дерево решения vs Semantic Map vs mount preview и т.д.) в главном окне задаётся **логикой композиторов** (`MainWindowHostSurfaceCompositor`, `DefaultSurfaceSlotInstrumentBindingProvider`, placement rules) и привязками Avalonia. Строка **`presentation`** задаёт **сколько якорей и на каких экранах**, но **не** декларативный выбор «в слоте `pfd` показывать инструмент A или B» для конкретного репозитория или пользователя. |
| 35 | |
| 36 | Потребность: **отделить топологию презентации** от **наполнения слотов внимания** и позволить команде или пользователю **переопределять карту инструментов** без правок кода — в духе данных о кабине, уже используемых для CDS ([0047](0047-cockpit-instrument-descriptor-and-slot-composition.md)). |
| 37 | |
| 38 | ## Решение |
| 39 | |
| 40 | **Принять** отдельный **конфигурируемый слой** — **карта размещения инструментов** для основных слотов кабины, хранимый в **TOML** и сливаемый по фиксированным правилам приоритета. |
| 41 | |
| 42 | **Инвариант 1 — граница с `presentation`:** строка **`presentation`** / **`[presentation_grammar]`** ([0017](0017-multi-window-workspace-and-agent-surfaces.md)) по-прежнему определяет только **топологию** (сколько групп `(…)`, какие якоря на каком дисплее, опциональные веса колонок). Карта инструментов **не** задаёт число мониторов и **не** заменяет якоря; рантайм сопоставляет **семантические слоты** (`pfd_primary`, `mfd_primary`) с конкретными парами **`surface_id` + `slot_id`** внутри продукта (в т.ч. при смене окна-хоста MFD), без требования писать `surface_id` в пользовательском/репозиторном TOML. |
| 43 | |
| 44 | **Инвариант 2 — публичные значения (alias):** в `workspace.toml` и в `[display.instrument_routing]` задаются **человекочитаемые** токены, которые рантайм приводит к каноническим **`instrument_id`** (`CockpitStandardInstrumentIds`): |
| 45 | |
| 46 | | Значение в TOML | Канонический `instrument_id` | |
| 47 | |-----------------|-------------------------------| |
| 48 | | `solution_explorer` | `solution_explorer_tree` | |
| 49 | | `workspace_map` | `workspace_navigation_map` | |
| 50 | | `ide_health` | `ide_health_status_v1` | |
| 51 | |
| 52 | Допустимо также указать **уже канонический** `instrument_id` (как в CDS), если нужен копипаст из диагностик. |
| 53 | |
| 54 | **Инвариант 3 — слои merge и конфликт одного ключа:** |
| 55 | |
| 56 | Три источника данных (от общего к частному): |
| 57 | |
| 58 | 1. Встроенный **бандл** IDE (дефолт продуктовой карты). |
| 59 | 2. **Репозиторный** слой — **`.cascade/workspace.toml`** с merge поверх бандла ([0021](0021-pfd-mfd-cockpit-attention-model.md) §2.1). |
| 60 | 3. **Пользовательский** слой — **`[display.instrument_routing]`** в `%LocalAppData%\CascadeIDE\settings.toml` ([0028](0028-user-settings-toml-localappdata-and-secrets.md)). |
| 61 | |
| 62 | **Внутри одного слоя-таблицы** `[instrument_routing]` ключи уникальны (`pfd_primary`, `mfd_primary`). |
| 63 | |
| 64 | **Между источниками** по умолчанию: **пользовательский слой сильнее репозиторного, репозиторный сильше бандла** (`user > repo > bundle`) для одной и той же семантики слота. |
| 65 | |
| 66 | **Флаг в пользовательском `settings.toml`:** **`prefer_repo_instruments_placement`** (bool). При **`true`** для совпадающих ключей побеждает **репозиторный/bundle** слой; при **`false`** или если ключ не задан — **`user > repo`**. |
| 67 | |
| 68 | **Инвариант 4 — валидация:** неизвестный ключ слота, неизвестный alias/`instrument_id` — **явная диагностика** при загрузке настроек (валидация `[display]`); для workspace — отладочный лог при игнорировании строки. |
| 69 | |
| 70 | **Инвариант 5 — без `surface_id` в публичном TOML:** пользователь и репозиторий задают только **`[instrument_routing]`** / **`[display.instrument_routing]`**; соответствие поверхностям `main_window_docked_grid` / `main_window_plus_mfd_host_top_level` выполняется в `InstrumentPlacementRuntime`. |
| 71 | |
| 72 | ### Публичный v1-контракт |
| 73 | |
| 74 | **Бандл / репо** (`UiModes/workspace.toml`, `.cascade/workspace.toml`): |
| 75 | |
| 76 | ```toml |
| 77 | [instrument_routing] |
| 78 | pfd_primary = "solution_explorer" |
| 79 | mfd_primary = "workspace_map" |
| 80 | ``` |
| 81 | |
| 82 | **Пользователь** (`settings.toml`): |
| 83 | |
| 84 | ```toml |
| 85 | [display.instrument_routing] |
| 86 | pfd_primary = "workspace_map" |
| 87 | ``` |
| 88 | |
| 89 | - `pfd_primary` и `mfd_primary` — ключи уровня продукта. |
| 90 | - Значения — **alias** из таблицы выше или канонический `instrument_id`. |
| 91 | |
| 92 | Низкоуровневый массив `[[instrument_placement_rules]]` с полями `surface_id` / `slot_id` **не используется** в публичном контракте v1 (внешних потребителей нет; DX — только таблица выше). |
| 93 | |
| 94 | ## Почему не только код |
| 95 | |
| 96 | - **Репозитории** различаются по тому, что считается «главным» в PFD (карта vs дерево vs оба в разных пресетах). |
| 97 | - **Эксперименты Flight** и продуктовые режимы должны переключать карту **данными**, а не ветками с разными композиторами. |
| 98 | - **Агенты и наблюдаемость:** CDS уже проецирует список инструментов ([0036](0036-cds-channel-compositor-surface-pipeline.md)); единый источник в TOML упрощает согласование «что заявлено» и «что в снимке». |
| 99 | |
| 100 | ## Альтернативы (отклонённые как основной путь v1) |
| 101 | |
| 102 | | Альтернатива | Почему не базовый выбор | |
| 103 | |--------------|-------------------------| |
| 104 | | Только правки в `MainWindowHostSurfaceCompositor` | Нет пользовательского и репо-слоя без форка. | |
| 105 | | Расширить только `presentation` литералами инструментов | Смешение топологии дисплеев и выбора виджетов; строка станет нечитаемой и сломает парсер по смыслу. | |
| 106 | | Только JSON в настройках | TOML уже канон для IDE и workspace; один стиль предпочтительнее. | |
| 107 | |
| 108 | ## Последствия |
| 109 | |
| 110 | - Композитор хоста читает **эффективную карту** после merge. |
| 111 | - Тесты: unit на merge словаря, alias-резолвер, сценарий «другой инструмент в PFD при том же `presentation`». |
| 112 | - Документация: samples рядом с `settings.toml`. |
| 113 | |
| 114 | ## Статус реализации |
| 115 | |
| 116 | **Реализовано**: |
| 117 | - bundle/repo: `UiModes/workspace.toml` + `.cascade/workspace.toml` через `UiWorkspaceTomlMerger` и `UiModeCatalog.ApplyRepositoryWorkspaceTomlOverlay`; |
| 118 | - user: `[display.instrument_routing]` и `prefer_repo_instruments_placement` в `%LocalAppData%\CascadeIDE\settings.toml`; |
| 119 | - единый runtime: `InstrumentRoutingAliasResolver`, `InstrumentPlacementRuntime`, `MainWindowHostSurfaceCompositor`. |
| 120 | |
| 121 | ## Открытые вопросы |
| 122 | |
| 123 | - **Числовой приоритет** у строки не требуется в v1: достаточно **слоя merge** между бандл / репо / user; при необходимости тонкой настройки в одном файле — позже, отдельным полем. |
| 124 | - Нужно ли версионирование схемы секции (`schema_version` внутри блока) для миграций. |
| 125 | - Связь с **mount-слоем** (`InstrumentMountPolicyRules`, `use_skia_instrument_mount`): **ортогонально** — style остаётся про визуал, карта из этого ADR — про *какой* `instrument_id` в слоте. |
| 126 | |