| 1 | # ADR 0010: Данные UI-режимов (Focus / Balanced / …) в TOML |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-04-02 |
| 5 | **Обновлено:** 2026-04-25 — в `[capabilities]` ключи **IDE Health**: `ide_health_*`. Подробности — [§ История](#adr0010-history). |
| 6 | |
| 7 | ## Связанные ADR |
| 8 | |
| 9 | | ADR | Роль | |
| 10 | |-----|------| |
| 11 | | [0003](0003-debug-ui-mode-separate-from-power.md) | Отдельный UI-режим Debug (не кокпит Power) | |
| 12 | | [0006](0006-presentation-layers-and-feature-slices.md) | Слои, вертикальные срезы и роль MainWindowViewModel | |
| 13 | | [0017](0017-multi-window-workspace-and-agent-surfaces.md) | мультиоконность и топология | |
| 14 | | [0022](0022-workspace-health-lexicon.md) | канон имён и эволюция для **IDE Health** (пересечение с таблицей `ide_health_*` ниже). | |
| 15 | |
| 16 | ### Вне ADR |
| 17 | |
| 18 | | Документ | Роль | |
| 19 | |----------|------| |
| 20 | | [`attention-zone-panel-playbook-v1.md`](../design/attention-zone-panel-playbook-v1.md) | зона ↔ панель ↔ топология | |
| 21 | |
| 22 | ### Снимок реализации |
| 23 | |
| 24 | | Элемент | Значение | |
| 25 | |---------|----------| |
| 26 | | — | загрузчик TOML, `UiModeCatalog`, capabilities, bundle `UiModes/` | |
| 27 | | — | override и `docs/ui-ux` — по мере нужды | |
| 28 | |
| 29 | ## Резюме |
| 30 | |
| 31 | - Режимы UI (Focus/Balanced/…) — **данные в TOML** (`UiModes/`), не только константы в C#. |
| 32 | - Bundle + merge с `.cascade/workspace.toml`; override без обратной записи динамики в шип. |
| 33 | - **`[capabilities]`** и ключи **`ide_health_*`** — контур IDE Health в том же каталоге режимов. |
| 34 | - Tomlyn, snake_case; JSON не дублируем как второй канон конфигов. |
| 35 | |
| 36 | --- |
| 37 | ## Контекст |
| 38 | |
| 39 | Сейчас **спеки режимов** (видимость панелей, группы редактора, слот темы, ширина развёрнутого чата и т.д.) зашиты в **C#** (`UiModeLayoutRegistry`, связанные константы). Глобальные размеры хрома (сплиттеры, минимумы строк и пр.) собраны в **`UiWorkspaceLayoutDimensions`** и применяются через **`UiWorkspaceLayout`**. |
| 40 | |
| 41 | Потребность: **менять раскладку режимов без пересборки** (дизайн, пресеты, эксперименты), в одном стиле с уже принятым в продукте хранением настроек в **`settings.toml`** (Tomlyn уже в зависимостях). |
| 42 | |
| 43 | **JSON для этого не предлагается** как основной формат: пользовательские настройки уже TOML; дублировать второй «официальный» текстовый формат для конфигов режимов нежелательно. |
| 44 | |
| 45 | ## Решение |
| 46 | |
| 47 | <a id="adr0010-p1"></a> |
| 48 | 1. **Источник данных режимов** — **несколько файлов TOML**: **индекс** задаёт **порядок и полный список id** режимов в меню (включая встроенные и **дополнительные**, см. ниже) + **отдельный файл на режим** (`Focus.toml`, `Balanced.toml`, …; шипнутый набор дублирует дефолты из `UiModeLayoutRegistry` / `DefaultsForFamily`), чтобы диффы и смысл «один режим — один файл» были очевидны. Если файла режима нет — загрузчик берёт те же дефолты из кода. Версионируем с приложением (как темы: копия рядом с exe или встроенный ресурс + fallback на диск). **Расположение шипнутого набора** — **подкаталог `UiModes/`** рядом с exe (тот же паттерн, что **`Themes/`**: копия в вывод сборки рядом с exe). Отдельный **путь в настройках пользователя** — возможное расширение позже, **в первой версии не обязательно**. |
| 49 | <a id="adr0010-p2"></a> |
| 50 | 2. **Глобальные метрики хрома** (ширина дерева по умолчанию, толщина сплиттеров, минимумы высот нижней зоны, политика «свёрнуто» и т.п.) — **один раз** в отдельном файле **`workspace.toml`** рядом с индексом и режимами (не копировать в каждый `Debug.toml`). |
| 51 | <a id="adr0010-p3"></a> |
| 52 | 3. **Схема окна (инварианты):** число колонок **MainGrid**, смысл колонок, привязка контролов к колонкам — **намеренно остаются в коде/XAML** в рамках этого ADR. TOML описывает **режимы поверх этого каркаса** (видимость, метрики, слот темы и т.д.), а не альтернативную разметку окна. Возможный вынос схемы окна в данные — **отдельное ADR**, если появится продуктовая необходимость (несколько каркасов, плагины и т.п.). |
| 53 | <a id="adr0010-p4"></a> |
| 54 | 4. **Загрузка при старте:** десериализация в модель, совместимую по смыслу с `UiModeLayoutSpec` (+ числа вроде ширины правой колонки в **развёрнутом** виде для режима, если остаётся в данных). |
| 55 | <a id="adr0010-p5"></a> |
| 56 | 5. **Fallback:** если файл отсутствует, битый или не прошёл валидацию — **встроенные значения по умолчанию** из кода, без падения IDE. См. ниже про роль этих дефолтов. |
| 57 | <a id="adr0010-p6"></a> |
| 58 | 6. **Версия схемы:** поле **`schema_version`** — **только в корне файла индекса** (первый читаемый файл набора). Оно задаёт версию **всего** бандла `UiModes/` — индекс, **`workspace.toml`**, per-mode файлы. В **`workspace.toml` отдельного `schema_version` нет** (избегаем двух чисел и рассинхрона). Миграции формата — по одному номеру из индекса. |
| 59 | <a id="adr0010-p7"></a> |
| 60 | 7. **Валидация при загрузке (минимум):** неизвестный **`theme_slot`** → fallback; в **шипнутом** наборе отсутствует спека для **обязательного** id → предупреждение + встроенная спека из кода. **Минимальный набор обязательных id** (который продукт не имеет права «потерять» в данных) задаётся **в коде** — сейчас это совпадает с **`UiModeLayoutRegistry.OrderedModeIds`**. Индекс может перечислять **больше** id, чем этот минимум (**дополнительные пользовательские или пресетные режимы**). См. ниже про наследование и отдельный пункт меню без замены `Debug`. |
| 61 | <a id="adr0010-p8"></a> |
| 62 | 8. **Capabilities** — тип **`UiModeCapabilities`** (`Features/UiChrome/UiModeCapabilities.cs`). В **`UiModes/<id>.toml`** ключи **предметные** (что в интерфейсе), в **snake_case**; десериализация через **`CascadeTomlSerializer`** (имена свойств модели в PascalCase → snake_case). Мердж: явное значение → при **`inherits`** — от родителя → иначе **`DefaultsForFamily`**. API: **`GetCapabilities`**, **`GetWindowTitleOverride`**. |
| 63 | |
| 64 | | Ключ TOML | Смысл | |
| 65 | |-----------|--------| |
| 66 | | `active_task_strip` | Полоса активной задачи / Task Cockpit под тулбаром | |
| 67 | | `main_window_title` | Полный заголовок главного окна | |
| 68 | | `quick_actions` | Быстрые действия у задачи | |
| 69 | | `agent_operations_panel` | Блок операций агента в чате (Balanced) | |
| 70 | | `agent_trace` | Панель trace агента (Power) | |
| 71 | | `autonomous_agent_telemetry` | Кокпит Power: явный доступ к выводу (терминал и подсказки); **не** канал IDE Health | |
| 72 | | `ide_health_on_terminal_tab` | Дубль IDE Health на вкладке «Терминал» (Power) | |
| 73 | | `ide_health_main_column_span` | Column span области IDE Health в основной сетке (Power) | |
| 74 | | `ide_health_strip` | Показывать полосу IDE Health под редактором | |
| 75 | | `ide_health_surface` | `bottom_strip` или `dedicated_page` — слой представления IDE Health | |
| 76 | | `instrumentation_tabs` | Вкладки событий/тестов/отладки в нижнем доке | |
| 77 | | `hypotheses_tab` | Вкладка «Гипотезы» | |
| 78 | | `risk_summary_card` / `result_summary_card` | Карточки риска и результата в чате | |
| 79 | |
| 80 | ### Слои модели (шпаргалка для автора `UiModes/*.toml`) |
| 81 | |
| 82 | Чтобы не путать **id режима**, **раскладку**, **семью** и **capabilities**, удобно держать в голове такие уровни: |
| 83 | |
| 84 | | Слой | Что это | Откуда берётся | |
| 85 | |------|---------|----------------| |
| 86 | | **Id режима** | Стабильная строка (пункт меню, ключ в каталоге, имя файла `Id.toml`) | `index.toml` → `modes`, плюс таблица встроенных id в коде для fallback | |
| 87 | | **Раскладка** | Видимость панелей, число групп редактора, слот темы, флаги вроде «выбирать вкладку терминала» | База: `UiModeLayoutRegistry` для встроенных id; мердж с полями текущего `*.toml`; при **`inherits`** — база = разрешённый родитель | |
| 88 | | **`workspace.toml`** | Общие числа хрома (сплиттеры, ширины чата по правилу Power / AgentChat / остальные, минимумы строк и т.д.) | Один файл на бандл; без копирования в каждый режим | |
| 89 | | **`family`** | Продуктовая роль: Focus, Balanced, Power, AgentChat, Debug — влияет на **дефолты capabilities** и ветки в коде (`UiModeFamily`) | Порядок: явный **`family`** в файле режима → при **`inherits`** — семья **разрешённого родителя** → для встроенных id — **таблица в коде** по id → иначе **Balanced** | |
| 90 | | **Capabilities** | Что показывать в интерфейсе (гипотезы, quick actions, хром кокпита Power и т.д.) | База: при **`inherits`** — **capabilities разрешённого родителя**; без **`inherits`** — **`DefaultsForFamily(family)`**. Поверх — явные ключи в `*.toml` | |
| 91 | |
| 92 | Узкий ключ в том же **`workspace.toml`** про **где монтировать превью Markdown** (не путать с будущей общей топологией нескольких `TopLevel`) — [0026](0026-markdown-preview-surfaces-and-placement.md). |
| 93 | |
| 94 | **Конечный пользователь IDE** в комбо «режим интерфейса» видит в основном **id пресета**; оси **`family`** / **capabilities** ему не обязательны, пока в UI нет отдельных переключателей под них. |
| 95 | |
| 96 | ### Топология презентации зон (будущее расширение) |
| 97 | |
| 98 | Сейчас в коде одна топология — **`MainWindowDockedGrid`** (`AttentionLayoutSurfaceKind`, одно главное окно, колонки `MainGrid`). Когда в продукте появятся **альтернативы** (несколько `TopLevel`, сценарии [0017](0017-multi-window-workspace-and-agent-surfaces.md)), **логично** связывать выбранную топологию с merge слоёв [0010](0010-ui-modes-toml-configuration.md) и отдельным учётом **персональной** раскладки по мониторам (см. следующий абзац). **Без обратной записи** динамического ресайза окон в шипнутые файлы (см. подраздел про рантайм ниже). |
| 99 | |
| 100 | **Не смешивать** с картой **панель → зона** (`attention_zone_panels` / `AttentionZonePanelRuntime`): там семантика «какая панель в какой зоне», здесь — **в какой геометрии** (одно окно vs несколько) воплощены регионы. Подробнее: [`attention-zone-panel-playbook-v1.md`](../design/attention-zone-panel-playbook-v1.md). |
| 101 | |
| 102 | **Поля `presentation` / `zone_screen_layout` (корень `settings.toml`) и токены грамматики** в секции **`[presentation_grammar]`** (`screen_markers`, `screen_separator`, `zone_separator`, литералы якорей `pfd_zone_identifier` / `forward_zone_identifier` / `mfd_zone_identifier`) — строка раскладки по **физическим дисплеям** из [0017](0017-multi-window-workspace-and-agent-surfaces.md) («Несколько мониторов»), например `(PFD+Forward) (MFD)` или **`(P+F) (M)`** при коротких идентификаторах в TOML ([таблица](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-presentation-grammar)); **между якорями внутри одной пары скобок** допускается **только** литерал **`zone_separator`** (**`Z`**, по умолчанию **`+`**); при **`Z = "|"`** в строке — только **`|`**, без подстановки «второго стиля». **EBNF** — [0017](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-presentation-ebnf). **Где хранить:** **прежде всего** **`settings.toml`** пользователя ([0028](0028-user-settings-toml-localappdata-and-secrets.md)). Репозиторный **`.cascade/workspace.toml`** ([0021](0021-pfd-mfd-cockpit-attention-model.md) §2.1) — для **командных** соглашений по панелям и зонам, **не** для обязательной для всех схемы мониторов; при merge **пользовательские** значения **`presentation`** и токенов **перекрывают** одноимённые в бандле/репо. **Альтернативное имя ключа** — **`zone_screen_layout`**; в одном конфиге задаётся **одно** из двух. Перечисление значений `AttentionLayoutSurfaceKind` и **валидация** — в реализации; отдельный ADR под схему TOML **не требуется**, пока объём правил укладывается в этот подпункт. |
| 103 | |
| 104 | **`inherits = "BaseId"`** — один родитель: наследуется **весь** уже разрешённый режим (раскладка, capabilities, ширина чата, полоса задачи, заголовок окна — по правилам мержа в коде). В дочернем файле задаются **только отличия** от родителя. Отдельная секция вроде `[inherits]` со списком «какие поля наследовать» **не используется**: при такой модели список дельт в файле и так короче, чем перечисление срезов. |
| 105 | |
| 106 | ### Порядок разрешения `family` (формально) |
| 107 | |
| 108 | 1. Если в **`Id.toml`** задано **`family = "…"`** (регистр не важен) — используется оно. |
| 109 | 2. Иначе, если задано **`inherits`** — берётся **`family`** уже разрешённого родителя (рекурсивно). |
| 110 | 3. Иначе — для известных встроенных id (`Focus`, `Balanced`, …) — **`BuiltinFamily(id)`** в коде. |
| 111 | 4. Иначе — **Balanced** (неизвестный id без явной семьи). |
| 112 | |
| 113 | ### Примеры: `inherits` и опционально `family` |
| 114 | |
| 115 | **Дополнительный режим как Debug, но с полосой задачи и своим заголовком** — без дублирования всей спеки: |
| 116 | |
| 117 | `index.toml` (фрагмент; полный индекс см. шипнутый `UiModes/index.toml`): |
| 118 | |
| 119 | ```toml |
| 120 | schema_version = 1 |
| 121 | modes = [ "Focus", "Balanced", "Power", "AgentChat", "Debug", "MySuperDebug" ] |
| 122 | ``` |
| 123 | |
| 124 | `MySuperDebug.toml`: |
| 125 | |
| 126 | ```toml |
| 127 | inherits = "Debug" |
| 128 | |
| 129 | active_task_strip = true |
| 130 | main_window_title = "CascadeIDE — мой отладочный пресет" |
| 131 | ``` |
| 132 | |
| 133 | Поле **`family` не нужно**: семья остаётся **Debug** (как у разрешённого родителя), поведение гипотез/инструментирования — как у отладки. |
| 134 | |
| 135 | **Цепочка из двух уровней:** каждый файл задаёт только дельту; корень цепочки — встроенный id с полной спекой (или другой уже разрешённый режим). |
| 136 | |
| 137 | ```toml |
| 138 | # DeepWork.toml — ответвление от Focus |
| 139 | inherits = "Focus" |
| 140 | editor_group_count = 1 |
| 141 | main_window_title = "CascadeIDE — Deep work" |
| 142 | |
| 143 | # MyDeep.toml — ответвление от DeepWork |
| 144 | inherits = "DeepWork" |
| 145 | chat_expanded_width_pixels = 400 |
| 146 | ``` |
| 147 | |
| 148 | Оба новых id должны быть перечислены в **`modes`** в `index.toml`. |
| 149 | |
| 150 | **Редкий случай: `inherits` от `Debug` и явный `family`** (ось `UiModeFamily` отличается от родителя; см. пояснение после примера): |
| 151 | |
| 152 | ```toml |
| 153 | inherits = "Debug" |
| 154 | family = "Balanced" |
| 155 | ``` |
| 156 | |
| 157 | Раскладка — мердж спеки **Debug** с полями файла. Ось **`family`** для кода (`UiModeFamily`, предикаты вроде **`IsDebugFamily`**) становится **Balanced**. Важно: при **`inherits`** базовый слой **capabilities** в загрузчике берётся от **разрешённого родителя** (`parentResolved.Capabilities`), а не от **`DefaultsForFamily(family)`** — то есть флаги интерфейса по умолчанию остаются «как у Debug», пока их не переопределить ключами в том же `*.toml`. Если когда-нибудь понадобится стартовать capabilities от **`DefaultsForFamily`** выбранной семьи при наследовании раскладки — это отдельное изменение контракта загрузчика. |
| 158 | |
| 159 | *См. также подраздел **`inherits` и `family`: это не одно и то же** ниже — там про помодульный мердж и ширину чата.* |
| 160 | |
| 161 | ### Динамический ресайз после старта и TOML (источник правды) |
| 162 | |
| 163 | Файлы **`UiModes/*.toml`**, **`workspace.toml`** и индекс набора режимов — **пресет**: они задают раскладку и метрики **на момент загрузки** конфигурации (старт приложения и, если когда-нибудь появится, явная команда «перечитать конфиг»). Это **не** живой журнал геометрии окна. |
| 164 | |
| 165 | **Перетаскивание сплиттеров, изменение ширин/высот панелей и любой динамический ресайз хрома после запуска** — **рантайм-состояние в памяти**. Оно **ни при каком сценарии по умолчанию не записывается обратно** в шипнутые рядом с exe **`UiModes/*.toml`** и **`workspace.toml`**: ни при каждом движении сплиттера, ни при смене режима, ни при выходе из IDE. Так редактор и диффы по репозиторию остаются **источником дизайна режима**; случайный дрейф окна пользователя не портит файлы пресета. |
| 166 | |
| 167 | Если позже понадобится **сохранять** геометрию между сеансами, это оформляется **отдельным каналом** (например поля в **`settings.toml`**, отдельный пользовательский override в `%LocalAppData%` — см. «Границы» ниже), а **не** обратной записью в дистрибутивные TOML режимов. |
| 168 | |
| 169 | **MCP (`ide_set_panel_size` и аналоги)** и снимки UI для тестов/агента меняют **текущую** раскладку в процессе работы; контракт тот же: **не** считать, что вызов перезаписывает TOML на диске, пока это явно не задокументировано как отдельная операция «экспорт пресета в файл». |
| 170 | |
| 171 | ### Один и тот же `Debug` vs отдельный `MySuperDebug` (наследование) |
| 172 | |
| 173 | - **Правка `Debug.toml` или override на `Debug`** меняет **сам** режим `Debug` для того набора файлов, к которому относится конфиг: **штатный Debug в том виде, как в дистрибутиве, рядом не живёт** — это ожидаемо, если цель именно «переопределить Debug». Для сценария «и **стоковый** Debug доступен, и мой вариант рядом» нужны **два разных id**. |
| 174 | - **Дополнительный режим с новым id** (например `MySuperDebug`), по смыслу близкий к `Debug`, но с другой раскладкой под контекст: объявляется в **индексе** (отдельная строка в меню, своё сохранённое значение в настройках), отдельный файл **`MySuperDebug.toml`**. Чтобы не дублировать всю спеку, в файле режима поддерживается **наследование от базового id**, например поле **`inherits = "Debug"`** (имя уточняется при реализации): загрузчик **мержит** разрешённую спеку `Debug` с переопределениями из `MySuperDebug.toml`. Так **не нужно** копипастить десятки полей и **не нужно** менять C# только ради нового id — достаточно индекса + файла режима (и при необходимости общих правил для «неизвестных» id в коде: разрешённые базы, fallback). |
| 175 | - **Подпись в UI** для id (отображаемое имя вместо сырого `MySuperDebug`) — при необходимости отдельные поля в индексе/TOML или локализация; **не** смешивать с обязательным минимумом id из [п. 7](#adr0010-p7). |
| 176 | |
| 177 | ### Семантика для кода: «family» и устаревшие флаги `Is*Mode` |
| 178 | |
| 179 | **Контекст (проблема до одной оси):** проверки вроде «это Debug для гипотез?» через **`UiMode == "Debug"`** или булевы **`IsDebugMode`** ломаются для производного id (**`MySuperDebug`**): строка id другая, а продуктовый смысл «семья отладки» тот же. |
| 180 | |
| 181 | **Текущий код** уже переведён на **`UiModeFamily`** и предикаты расширения — см. подраздел **«Реализация в коде»** ниже. Таблица вариантов и **`family` в TOML** — про **данные после загрузчика**; там **`family`** после мержа должна попадать в ту же ось, что и сегодняшний `UiModeFamily`. |
| 182 | |
| 183 | Дополнительно, **`NormalizeUiMode`** сейчас сводит неизвестный режим к **`Balanced`**. Режимы с **новыми id** из индекса должны **сохраняться** (после проверки «id есть в загруженном списке»), иначе пользовательский `MySuperDebug` превратится в Balanced без предупреждения. |
| 184 | |
| 185 | **История обсуждения альтернатив** (зафиксировано для контекста; **реализован подход A**): |
| 186 | |
| 187 | | Подход | Суть | Плюсы | Минусы | |
| 188 | |--------|------|--------|--------| |
| 189 | | **A. Семейство (`family`) в данных** | После мержа в результирующей модели есть поле **`family`** (enum или строка: `debug`, `power`, …). В TOML — опционально **`family = "debug"`**; если не задано — **наследуется от базы** при `inherits` (у корня цепочки — как у встроенного id). Код переводит **`IsDebugMode`** на **`family == debug`** (или переименование в **`IsDebugFamily`**). | Явная семантика; `MySuperDebug` без копипаста получает «debug» через наследование; проще тестировать. | Нужна миграция существующих проверок по строке id; перечень `family` всё равно конечный в коде. | |
| 190 | | **B. Только цепочка `inherits`** | «Отладочность» выводится обходом inherits до известного встроенного id (**`ResolvesToBuiltIn("Debug")`**). | Не дублировать поле `family` в TOML. | Сложнее рассуждать и отлаживать; глубина/циклы; неочевидно, когда оборвать цепочку для UX. | |
| 191 | | **C. Явный `family` в каждом TOML без автонаследования** | Каждый режим, включая производные, пишет `family` руками. | Максимально явно. | Дублирование; легко ошибиться и задать `MySuperDebug` без `family`. | |
| 192 | | **D. Только точное совпадение id** | `IsDebugMode` остаётся `== "Debug"`; производные режимы **не** считаются Debug для гипотез и т.д. | Минимум кода. | Противоречит сценарию «MySuperDebug как Debug»; пользователь вводит в заблуждение. | |
| 193 | |
| 194 | **Реализовано:** подход **A** — в результирующей модели после загрузки есть **`UiModeFamily`**; в TOML опционально **`family`**; проверки в VM/вью — по семье и capabilities, не по сырой строке id там, где нужна продуктовая роль. |
| 195 | |
| 196 | #### `inherits` и `family`: это не одно и то же (разделение ответственности) |
| 197 | |
| 198 | - **`inherits`** задаёт **разрешённого родителя**: от него берутся **`UiModeLayoutSpec`** и **базовый слой `UiModeCapabilities`** (не только «каркас»); дочерний файл накладывает **помодульные** переопределения. Для **`chat_expanded_width_pixels`**: явное значение в файле → иначе у родителя по `inherits` → иначе у корневого режима — **`workspace.toml`** (через **`UiWorkspaceLayoutRuntimeMetrics`**) и правило Power / AgentChat / остальные. |
| 199 | - **`family`** отвечает за **продуктовую роль** в коде: какие вкладки/инструменты/политики считать «режимом отладки», «power» и т.д. Это **другое измерение**, не дублирение `inherits`: теоретически можно представить раскладку, унаследованную от `Debug`, но явно переопределённую семантику (редкий кейс). |
| 200 | |
| 201 | Чтобы не казалось, что в TOML **две ручки про одно**, правило умолчаний может быть таким: |
| 202 | |
| 203 | 1. Для **встроенных** id **`family`** задаётся **таблицей в коде** (тот же смысл, что сейчас заложен в id). |
| 204 | 2. Для режима с **`inherits`** поле **`family` в TOML не обязательно**: по умолчанию **`family` = семья разрешённого родителя** (например `MySuperDebug` → `inherits = "Debug"` → **Debug**). Тогда в файле достаточно **`inherits`**, без отдельной строки `family`, пока семантика совпадает с базой. |
| 205 | 3. **Явный `family` в TOML** — когда нужно **перебить** вычисленную семью для **`UiModeFamily`** (редко; см. ограничение про базу capabilities при **`inherits`** в примере выше). |
| 206 | |
| 207 | Так **`inherits` не «задаёт family» сам по себе** — он задаёт **цепочку родителя** для мержа раскладки и capabilities; **family** либо задаётся явно, либо наследуется от родителя, либо берётся из таблицы встроенных id, либо **Balanced** для неизвестного id. Разделение: мердж данных по файлу/родителю vs ось семьи для кода. |
| 208 | |
| 209 | **Связанные места (не исчерпывающе):** `MainWindowViewModel.Presentation` (**`UiModeFamily`**, инструментация), **`UiChromeViewModel.NormalizeUiMode`**, bloom по строке режима, **`GetChatPanelExpandedWidthPixels`**, MCP-обработчики с особыми правилами для режима отладки. |
| 210 | |
| 211 | #### Не плодить `Is*Mode` в VM |
| 212 | |
| 213 | Набор булевых **`IsFocusMode`**, **`IsPowerMode`**, **`IsDebugMode`** и т.д. был типичным **запахом**. В коде они **сняты** в пользу **`UiModeFamily`** и предикатов (см. **«Реализация в коде»**). После загрузчика TOML предпочтительно иметь **один вычисляемый контекст** после загрузки: enum **`UiModeFamily`** и/или компактный объект **capabilities** (что показывать: гипотезы, элементы кокпита Power, …), заполняемый из резолвнутой спеки и правил по умолчанию. **Не** возвращать очередной `IsNewThingMode` и не плодить сравнения с **сырой строкой id** там, где нужна **семья**. |
| 214 | |
| 215 | #### Реализация в коде (зафиксировано до загрузчика TOML) |
| 216 | |
| 217 | Это **не отдельный документ**: договорённости по оси режима фиксируются **здесь**, в ADR про режимы и TOML, чтобы и люди, и агент не искали разрозненные заметки. |
| 218 | |
| 219 | | Элемент | Назначение | |
| 220 | |--------|------------| |
| 221 | | **`UiModeFamily`** + **`UiModeFamilyResolver.FromNormalizedMode`** | Одна ось после **`NormalizeUiMode(UiMode)`**; производные id маппятся в семью так же, как сегодня встроенные строки. | |
| 222 | | **`UiModeFamilyExtensions`** (`IsFocusFamily`, `IsBalancedFamily`, `IsPowerFamily`, `IsAgentChatFamily`, `IsDebugFamily`) | Симметричные предикаты вместо размножения **`== UiModeFamily.*`** по VM. | |
| 223 | | **Доменное имя там, где не хватает «голого» enum** | Например приватное **`AutonomousCockpitActive`** в автономной сессии: смысл «кокпит автономного агента», внутри — тот же **`IsPowerFamily()`**. | |
| 224 | | **XAML** | Привязки к **`UiModeFamily`** через конвертеры **`UiModeFamilyEq` / `UiModeFamilyNe`** (параметр — имя члена enum). | |
| 225 | | **После TOML** | Загрузчик мержит спеку, **`family` → `UiModeFamily`**, **`UiModeCapabilities`** (и опционально **`main_window_title`**), без отката к набору **`Is*Mode`**. | |
| 226 | |
| 227 | ### Fallback и встроенные дефолты (код vs TOML) |
| 228 | |
| 229 | Встроенный fallback **не претендует** быть «главным» источником красивой раскладки: его задача — **предсказуемо поднять IDE**, если данных на диске нет или они битые. **Нормальный путь** — шипнутые TOML рядом с exe; тогда fallback срабатывает редко (чистая установка, сломанный каталог, разработка без копии файлов). |
| 230 | |
| 231 | Качество текущих констант в коде (в том числе режимы вокруг **Power**) может быть **средним** — это **отдельная** ветка работы: доводка дефолтов, выравнивание с дизайном, возможно приближение built-in к содержимому шипнутых файлов. Это **не блокирует** механику «TOML + fallback из кода»: сначала надёжная загрузка и тесты, **параллельно или позже** — полировка встроенных значений (или генерация одного из другого в сборке — вне скоупа этого ADR, пока не понадобится). |
| 232 | |
| 233 | ### Видимость панелей vs «0 px» |
| 234 | |
| 235 | В конфиге режима и в смысле продукта **первична семантика видимости** (`visible` / `hidden` для панели; при необходимости позже — отдельное состояние вроде «свёрнуто с полоской», если появится политика не нулевой полоски). **«Скрыто» не должно задаваться главным образом как `width = 0`** в режиме TOML: нулевая ширина колонки и скрытый сплиттер — **производное** от правил видимости и глобальных метрик из `workspace.toml` (и кода инвариантов). Так одна и та же идея покрывает **любую** сворачиваемую колонку/зону, не только чат. |
| 236 | |
| 237 | ## Границы (что точно не входит в этот ADR) |
| 238 | |
| 239 | - **Схема окна** (каркас MainGrid, новые колонки/зоны из данных) — см. [п. 3](#adr0010-p3) решения; не входит. |
| 240 | - Подсветка синтаксиса **`.toml` в редакторе** — отдельно (см. [EDITOR-LANGUAGES.md](../EDITOR-LANGUAGES.md): грамматики TOML в бандле нет); на загрузку конфига не влияет. |
| 241 | - **Пользовательский override** в `%LocalAppData%\…` поверх шипнутого файла — возможное расширение **после** базовой схемы, не обязательно в первом коммите. |
| 242 | |
| 243 | ## Последствия |
| 244 | |
| 245 | - **Плюсы:** единый человекочитаемый формат с настройками; правки режимов без компилятора; проще ревью диффов раскладки; дополнительные режимы с **наследованием** без дублирования спеки и без обязательной правки C# под каждый новый id. |
| 246 | - **Минусы:** парсинг, ошибки на диске, тесты на fallback; документация полей TOML для контрибьюторов; загрузчик с **мержем** `inherits`; комбо режимов в UI — от **загруженного индекса** (код хранит **минимум** для валидации и fallback); **`family`** и **capabilities** должны оставаться согласованы с **`UiModeFamily`** и **`UiModeCapabilities.DefaultsForFamily`**; **`NormalizeUiMode`** сохраняет пользовательские id из индекса; **в коде VM булевые `Is*Mode` сняты** (см. «Реализация в коде»). Основные **`docs/ui-ux`** приведены к **`UiModeFamily`** / capabilities (обзор: **`docs/ui-ux/ui-modes-overview-v1.md`**). |
| 247 | - **Связь с MCP:** поведение `ide_set_panel_size` и снимков UI не должно ломаться; контракт с диском — см. подраздел **«Динамический ресайз после старта и TOML»** выше (рантайм и MCP **не** перезаписывают `UiModes/*.toml` / `workspace.toml` по умолчанию). |
| 248 | |
| 249 | ## Документация (в scope) |
| 250 | |
| 251 | Навигационные и UX-доки должны описывать **`UiModeFamily`**, **capabilities** и при необходимости TOML, а не устаревшие булевы **`Is*Mode`**. Обзор для читателя: **`docs/ui-ux/ui-modes-overview-v1.md`**; макет окна и карта концепт→код — **`docs/ui-ux/cascade-ide-ui-layout-v1.md`**, **`docs/ui-ux/concept-to-implementation-map-v1.md`**. Исторические ADR (например сравнение подходов A–D) могут упоминать старые имена в прошедшем времени — это норма. |
| 252 | |
| 253 | ## Отклонённые альтернативы |
| 254 | |
| 255 | - **Только JSON** — расходится с `settings.toml` и выбранным стеком Tomlyn для пользовательских конфигов. |
| 256 | - **Только код** — оставляет текущее состояние; отклоняется как основной путь из-за цели «файл режима» без релиза. |
| 257 | |
| 258 | ## Следующий шаг (опционально) |
| 259 | |
| 260 | Пользовательский override каталога `UiModes/` из `%LocalAppData%` при продуктовой необходимости; точечные правки **`docs/ui-ux`** при изменении поведения режимов в коде. |
| 261 | |
| 262 | --- |
| 263 | |
| 264 | ## История изменений |
| 265 | |
| 266 | <a id="adr0010-history"></a> |
| 267 | |
| 268 | | Дата | Изменение | |
| 269 | |------|-----------| |
| 270 | | 2026-04-08 | намерение задать **топологию презентации** зон в TOML после появления альтернатив одному `MainGrid` ([0017](0017-multi-window-workspace-and-agent-surfaces.md)); см. подраздел ниже. | |
| 271 | | 2026-04-11 | **`presentation`** / **`zone_screen_layout`:** прежде всего **`settings.toml`**, не командный репо — [0017](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-presentation-grammar), [п. 4](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-p4); токены грамматики — секция **`[presentation_grammar]`** (без отдельных **`pfd_zone_alias`** — короткие имена через **`pfd_zone_identifier`** и т.д.); **`screen_markers`** / **`screen_separator`** / **`zone_separator`**, **EBNF** — [0017](0017-multi-window-workspace-and-agent-surfaces.md#adr0017-presentation-ebnf). | |
| 272 | | 2026-04-11 | якоря `adr0010-p1`…`p8` в разделе «Решение» и ссылки на [п. 3](#adr0010-p3), [п. 7](#adr0010-p7); соглашение — [README ADR](README.md#adr-anchors-policy). | |
| 273 | | 2026-04-25 | в **`[capabilities]`** ключи контура **IDE Health** в TOML: **`ide_health_*`** (свойства `UiModeCapabilities` — `IdeHealth*`). | |
| 274 | |