Forge
markdowndeeb25a2
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
581. Встроенный **бандл** IDE (дефолт продуктовой карты).
592. **Репозиторный** слой — **`.cascade/workspace.toml`** с merge поверх бандла ([0021](0021-pfd-mfd-cockpit-attention-model.md) §2.1).
603. **Пользовательский** слой — **`[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]
78pfd_primary = "solution_explorer"
79mfd_primary = "workspace_map"
80```
81
82**Пользователь** (`settings.toml`):
83
84```toml
85[display.instrument_routing]
86pfd_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
View only · write via MCP/CIDE