| 1 | # ADR 0017: Несколько окон приложения (мультиоконность), зоны экрана и поверхности агента |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-04-05 |
| 5 | **Обновлено:** 2026-04-18 — канон числа `TopLevel` и `display.screens` / topology; подробности — [§ История](#adr0017-history). |
| 6 | |
| 7 | ## Связанные ADR |
| 8 | |
| 9 | | ADR | Роль | |
| 10 | |-----|------| |
| 11 | | [0010](0010-ui-modes-toml-configuration.md) | режимы и TOML поверх каркаса окна; **топология презентации зон** — отдельный подраздел в 0010, не смешивать с `attention_zone_panels` | |
| 12 | | [0012](0012-floating-workspace-chrome.md) | плавающий хром; отдельные окна как одна из механик | |
| 13 | | [0021](0021-pfd-mfd-cockpit-attention-model.md) | семантика зон PFD / лобовое / MFD — **не** синоним числа окон | |
| 14 | | [0025](0025-sdk-attention-zones-and-capabilities.md) | зоны в SDK | |
| 15 | | [0016](0016-agent-client-protocol-external-agent.md) | внешний агент по ACP — ортогонально тому, *в скольких окнах* показывается встроенный UI | |
| 16 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | идентификаторы команд, **`Hotkeys/hotkeys.toml`** + пользовательский оверлей, палитра | |
| 17 | | [0031](0031-agent-chat-clarification-batches-and-threading.md) | пакеты уточнений в чате, не путать с PFD-подтверждениями | |
| 18 | | [0002](0002-debug-human-agent-parity.md) | паритет MCP | |
| 19 | | [0008](0008-mcp-contracts-and-testable-infrastructure.md) | контракты MCP | |
| 20 | | [0063](0063-instrument-deck-named-composition-one-anchor.md) | ось **instrument deck** и слово *presentation* vs CDS — см. § там; **ключи топологии** — ниже в этом ADR | |
| 21 | | [0120](0120-primary-work-surface-intercom-or-editor.md) | `primary_work_surface` — Intercom или Editor в Forward (ортогонально `presentation`) | |
| 22 | |
| 23 | ### Вне ADR |
| 24 | |
| 25 | | Документ | Роль | |
| 26 | |----------|------| |
| 27 | | [`attention-zone-panel-playbook-v1.md`](../design/attention-zone-panel-playbook-v1.md) | зона ↔ панель ↔ топология; в коде — `AttentionLayoutSurfaceKind` | |
| 28 | | [`concept-pfd-mfd-cascade-v1.md`](../ui-ux/concept-pfd-mfd-cascade-v1.md) | UX-концепт PFD/MFD (superseded текстом [0021](0021-pfd-mfd-cockpit-attention-model.md)) | |
| 29 | | [`concept-to-implementation-map-v1.md`](../ui-ux/concept-to-implementation-map-v1.md) | карта концепт → код | |
| 30 | | [skia-surfaces-vs-overlays-v1.md](../design/skia-surfaces-vs-overlays-v1.md) | Skia-поверхности vs оверлеи | |
| 31 | |
| 32 | ## Резюме |
| 33 | |
| 34 | - **Несколько `TopLevel`** — продуктовая модель разнесения зон по мониторам; семантика зон ([0021](0021-pfd-mfd-cockpit-attention-model.md)) **не равна** числу окон. |
| 35 | - Источник истины раскладки — строка **`presentation`** / **`zone_screen_layout`** в **`settings.toml`** ([0028](0028-user-settings-toml-localappdata-and-secrets.md)), парсер + `[presentation_grammar]`. |
| 36 | - **`(P+F)(M)`** → два окна; **`(P)(F)(M)`** → три; порядок групп в строке = слева направо на экранах. |
| 37 | - **`MfdHostWindow` / `PfdHostWindow`** — полный shell зоны; плейсмент только через `PresentationHostWindowPlacement`. |
| 38 | - MCP **`ide_get_ui_layout`** уже отдаёт все окна; roadmap — EICAS на втором экране, fallbacks, подтверждения агента ([«Открытые вопросы»](#adr0017-open-questions)). |
| 39 | |
| 40 | ## Состояние реализации (актуально по репозиторию) |
| 41 | |
| 42 | Ниже — **что уже есть в коде**, чтобы не путать с продуктовым backlog из [«Открытых вопросов»](#adr0017-open-questions). |
| 43 | |
| 44 | ### Уже реализовано |
| 45 | |
| 46 | | Область | Где в коде / что делает | |
| 47 | |--------|-------------------------| |
| 48 | | Разбор строки `presentation` | `Services/Presentation/PresentationParser.cs`, **`PresentationInnerEtoGrammar`** (Eto.Parse), `PresentationGrammarTokens`, `PresentationLayoutAnalyzer`, `PresentationAnchorKind`, **`PresentationAnchorSlot`** (веса); тесты `CascadeIDE.Tests/PresentationParserTests.cs`. **Веса зон** внутри группы — [§](#adr0017-zone-weights). **Колонки главного окна:** `PresentationMainGridColumnDefinitions.Get` → **`MainGridColumnDefinitions`** у `MainWindowViewModel`, применение в **`MainWindow.PresentationLayout.axaml.cs`** (`ColumnDefinitions.Parse` — Avalonia не биндит строку к `ColumnDefinitions` из VM); хвост MFD при двухякорном пресете — `340` или `0` при скрытии колонки под хост ([реализация](../../Services/Presentation/PresentationMainGridColumnDefinitions.cs)). | |
| 49 | | Топология ↔ дисплеи | `PresentationMonitorTopology.OrderScreensForPresentation` — сортировка `Screen` слева направо, затем сверху вниз (общая сетка координат; не primary ОС как якорь). | |
| 50 | | Плейсмент окон-хостов (`MfdHostWindow`, `PfdHostWindow`) | `Services/PresentationHostWindowPlacement` — целевой экран по индексу из пресета; иначе первый «не главный» экран; при сохранённых bounds — восстановление с прижатием к `WorkingArea`. На выделенном мониторе по умолчанию **`WindowState.Maximized`**; отключение — `[display]` **`maximize_presentation_host_windows_on_dedicated_screens`** (`DisplaySettings.MaximizePresentationHostWindowsOnDedicatedScreens`). | |
| 51 | | Инвариант плейсмента | Позиционирование и восстановление геометрии `PfdHostWindow` / `MfdHostWindow` — **только** через `PresentationHostWindowPlacement` (и общий жизненный цикл во `MainWindow.PresentationHostWindows.axaml.cs`); не плодить параллельную логику экрана / `WorkingArea` в других Views без расширения этого сервиса. | |
| 52 | | Персистентность геометрии хостов | `CascadeIdeSettings` / `[display]`: для MFD — `MfdHostWindowPixelX` / `PixelY` / `Width` / `Height`; для PFD — `PfdHostWindowPixelX` / `PixelY` / `Width` / `Height`; запись при `Closing` соответствующего окна (`Views/MainWindow.PresentationHostWindows.axaml.cs`). | |
| 53 | | Окно-хост MFD | `Views/MfdHostWindow.axaml` — полный `MfdShellView` ([п. 8](#adr0017-p8-mfd-host-wide)), тот же `DataContext`, что у `MainWindow`; слой подсветки MCP как у главного окна. Колонка под якорем Mfd в `MainWindow` — **один** `MfdShellView` (страницы: чат, терминал, обозреватель решения и т.д.), без отдельного зашитого сплита «дерево + shell»; второй `TopLevel` повторяет **тот же** вторичный контур ([skia-surfaces-vs-overlays-v1.md](../design/skia-surfaces-vs-overlays-v1.md)), не обязательно пиксель-в-пиксель по ширине. | |
| 54 | | Жизненный цикл | Нет отдельного пункта меню «второе окно» — источник истины строка `presentation` / `zone_screen_layout`; автозапуск при `Loaded` (см. `MainWindow.PresentationHostWindows.axaml.cs`): при подходящей топологии && `OpenPfdHostWindowOnStartup` / `OpenMfdHostWindowOnStartup` && достаточно мониторов — открываются `PfdHostWindow` и/или `MfdHostWindow`; опционально MCP `toggle_mfd_host_window` при той же топологии (`CanExecute`). При открытом хосте соответствующая колонка в `MainWindow` скрывается (`SetPfdHostWindowShellOpen` / `SetMfdHostWindowShellOpen`). | |
| 55 | | Топология внимания | `AttentionLayoutSurfaceKind`: при хостах — `MainWindowPlusMfdHostTopLevel`, `MainWindowPlusPfdHostTopLevel`, `MainWindowPlusPfdMfdHostTopLevel` (`MainWindowViewModel.Presentation.cs`). | |
| 56 | | MCP / снимки | `UiLayoutSnapshot`, роли `mfd_host` / `pfd_host` для соответствующих окон; `ide_get_ui_layout` по всем top-level. | |
| 57 | |
| 58 | ### Частично или вне текущего кода (остаётся roadmap) |
| 59 | |
| 60 | - **Пресет с тремя группами `(…) (…) (…)`** (любой порядок якорей, напр. `(P) (F) (M)` или `(M) (F) (P)`): по **канону** — **три** `TopLevel`, по одной зоне на окно; **пространственно** слева направо = порядок групп в строке (см. §«Пространственный канон»). **В коде:** `PresentationLayoutAnalyzer.IsTripleOneAnchorPerZonePreset`, индексы экранов для хостов — `TryGetPfdHostPresentationScreenIndex` / `TryGetMfdHostPresentationScreenIndex`; окна **`PfdHostWindow`** и **`MfdHostWindow`**, главное — Forward; плейсмент и persist — `PresentationHostWindowPlacement` / `[display]`. Остаётся roadmap: продуктовая доводка UX, fallback при смене мониторов, расширения MCP вне базового `ide_get_ui_layout`. |
| 61 | - **Fallback** при исчезновении монитора / сильной смене раскладки ОС: есть clamp к рабочим областям; отдельный UX «монитор недоступен» не описан здесь как обязательный. |
| 62 | - **Закрытие главного окна:** при сбросе `DataContext` у `MainWindow` вызывается закрытие хоста (`CloseMfdHostWindowIfOpen`); явная подписка на `MainWindow.Closing` для каскада — не зафиксирована в этом списке (зависит от жизненного цикла приложения). |
| 63 | - **EICAS / IDE Health на втором экране**, плотность чата/терминала — продуктовые открытые пункты ([«Открытые вопросы»](#adr0017-open-questions)). |
| 64 | - **Подтверждения агента** при двух `TopLevel` — направление в [п. 6](#adr0017-p6), детали реализации — открыты. |
| 65 | |
| 66 | --- |
| 67 | ## Контекст |
| 68 | |
| 69 | Сейчас **одно главное окно** (`MainWindow`) задаёт весь видимый кокпит: редактор, дерево, чат, колонка Mfd и т.д. Конфигурация режимов ([0010](0010-ui-modes-toml-configuration.md)) описывает **видимость и метрики поверх фиксированного каркаса**; альтернативная «схема окна» целиком в данных явно отложена. |
| 70 | |
| 71 | Потребность: **разнести роли по экрану** без обязательной ужимки всего в одну колонку — в том числе: |
| 72 | |
| 73 | - **Несколько физических мониторов** — в идеале **`(PFD) (Forward) (MFD)`** (три дисплея); см. подраздел «Несколько мониторов» и таблицу схем **`(PFD+Forward+MFD)`** при дефолтном **`Z`** или **`(PFD|Forward|MFD)`** при **`zone_separator = "|"`** / **`(PFD+Forward) (MFD)`**; или **один** широкий/изогнутый дисплей с одной парой скобок и тремя якорями в одном окне и явными зонами (см. концепт PFD/MFD, §6). |
| 74 | - **Вынести семантическую зону внимания на другой дисплей** — в первую очередь **MFD** (чат, терминал, trace, тяжёлые панели агента); при необходимости **PFD** или компактную полосу подтверждений/статуса. Продуктовая формулировка — **зона ↔ второй или третий монитор**, а не абстрактное «второе окно». В коде тип `MfdHostWindow`, в снимке MCP поле `role` = `mfd_host` для такого второго `TopLevel`. |
| 75 | - **Эксперименты с раскладкой** изолированно от стабильных пресетов — режим **Flight** как песочница; мультиоконность логично отрабатывать там первыми (см. §5 `concept-pfd-mfd-cascade-v1.md`). |
| 76 | |
| 77 | [0012](0012-floating-workspace-chrome.md) уже допускает **отдельные окна** для части хрома (телеметрия, полоски). **Базовый MCP под мультиоконность уже есть:** `ide_get_ui_layout` отдаёт JSON с массивом `windows` (по одному дереву на каждое открытое `Window`, роли `main` / `mfd_host` / `other`); поиск контрола по имени для инспекции/действий — с приоритетом главного окна и обходом остальных (`Cockpit/Surface/UiLayoutSnapshot`, `UiControlAppearance`). Этот ADR **расширяет предмет** с технического «несколько корней в снимке» на **продуктовую модель** нескольких окон (какие поверхности, жизненный цикл, состояние, связь с режимами и агентом); точечные **дополнения контракта** — при появлении новых типов окон или семантик регионов ([см. п. 7](#adr0017-p7)). |
| 78 | |
| 79 | ### Один монитор и три семантические зоны ([0021](0021-pfd-mfd-cockpit-attention-model.md)) |
| 80 | |
| 81 | Три пространственных якоря внимания (PFD, лобовое, MFD) **не требуют** трёх отдельных окон. Текущая реализация — **одно** главное окно, колонки `MainGrid`, топология `AttentionLayoutSurfaceKind.MainWindowDockedGrid` (см. playbook). |
| 82 | |
| 83 | **Опциональный** сценарий: разнести те же семантики по **нескольким `TopLevel` на одном физическом мониторе** (например очень широкий дисплей) — один процесс, та же карта панель→зона ([`AttentionZonePanelRuntime`](../design/attention-zone-panel-playbook-v1.md)), иная только **геометрия презентации**. **По умолчанию это не цель:** достаточно **главного окна + вынесенной зоны на другом мониторе** и/или **пресета внутри одного `TopLevel`** ([0021](0021-pfd-mfd-cockpit-attention-model.md), мотив FancyZones). Отдельно обсуждать **три связанных `TopLevel` на одном мониторе** имеет смысл только при явной обратной связи или ультрашироких сценариях; иначе риск и сложность перевешивают выигрыш над двумя окнами или сеткой в одном окне. |
| 84 | |
| 85 | **Не** считается аргументом за мультиоконность: «заблокировать перемещение окон **на уровне ОС**». Прилипание, сетка и предсказуемая раскладка достигаются в рамках **одного** `TopLevel` (сплиттеры, пресеты) или программным позиционированием без обещания запрета средствами оболочки; см. [0022](0022-mfd-visual-design-surface-axaml-blazor.md) (второй монитор как геометрия одного окна — ортогонально числу окон). |
| 86 | |
| 87 | <a id="adr0017-several-monitors"></a> |
| 88 | |
| 89 | ### Несколько мониторов |
| 90 | |
| 91 | **Обозначения (чтобы не смешивать «зоны на одном экране» и «число дисплеев»):** |
| 92 | |
| 93 | - **`( … )`** — **один физический дисплей** (одна область в конфигурации мониторов ОС). |
| 94 | - **Разделитель якорей внутри одной пары скобок** — **ровно** литерал **`Z`** из **`zone_separator`** (по умолчанию **`+`**). Несколько семантических якорей (**PFD**, **Forward**, **MFD**) на **этом же** дисплее (как колонки в одном `MainWindow`) записываются как **`anchor` `Z` `anchor` `Z` `anchor`**. В прозе этого ADR **`+`** и **`|`** — две привычные **иллюстрации** одного смысла; **в строке `presentation`** при **`Z = "+"`** допустимы только **`+`**, при **`Z = "|"`** — только **`|`** (без подстановки «синонима» в парсере). |
| 95 | - **Краткая запись** — задаётся **тем же** ключом **`pfd_zone_identifier`** / **`forward_zone_identifier`** / **`mfd_zone_identifier`**, например **`"P"`**, **`"F"`**, **`"M"`**; в строке `presentation` допустим **только** выбранный литерал (без второго «синонима» в TOML). |
| 96 | - **Несколько подряд `(…) (…) (…)`** — **разные** дисплеи; слева направо в тексте — удобная ориентировка, фактический порядок экранов задаёт геометрия и пресет. **Три дисплея под три якоря** — только **три пары скобок**, например **`(PFD) (Forward) (MFD)`**. Запись **без скобок** вроде голого `PFD | Forward | MFD` **двусмысленна** — в конфиге и доках не использовать; символ между якорями **внутри** скобок — это **`Z`**, не разделитель экранов. |
| 97 | |
| 98 | <a id="adr0017-display-screens-topology-naming"></a> |
| 99 | |
| 100 | ### Ёмкость нотации и направление имён конфига (`display.screens` / `topology`) |
| 101 | |
| 102 | **Намеренная «ёмкость»:** одна и та же строка топологии (**`presentation`** в корне `settings.toml`, в будущем — ключ **`topology`** внутри таблицы ниже) **намеренно смешивает в одном слое** (а) **сколько** групп дисплеев в пресете (несколько `(…) (…) (…)` — см. [§ «Несколько мониторов»](#adr0017-several-monitors)) и (б) **как эти группы соотносятся** друг с другом и с якорями (пространственный порядок в тексте, семантика зон внутри группы). Развести «число дисплеев» и «описание топологии» **отдельными** ключами можно было бы явнее; **цена** — длиннее и многословнее. Текущая нотация платит за краткость тем, что смыслы (а) и (б) **не разводятся** без контекста грамматики и примеров ([§ грамматика](#adr0017-presentation-grammar), [EBNF](#adr0017-presentation-ebnf)). |
| 103 | |
| 104 | **Направление переименования TOML (снять путаницу слова *presentation* с «презентацией» UI и с другими слоями — см. [0063 § CDS](0063-instrument-deck-named-composition-one-anchor.md#adr0063-cds-not-presentation)):** канон — вложить описание топологии в уже смысловой **`[display]`** ([0050](0050-declarative-instrument-zone-placement-toml.md), `Display` в модели): |
| 105 | |
| 106 | - Таблица уровня «вся топология в одном месте» (сегодня корневые ключи **`presentation`** / **`zone_screen_layout`** и связанные поля — см. код) → **`[display.screens]`** (группы экранов пресета, не «презентация» в бытовом смысле). |
| 107 | - Строка EBNF / размещения якорей — ключ **`topology`** (явное имя роли). |
| 108 | - Грамматика: секция **`[presentation_grammar]`** → **`[display.screens.grammar]`** (или согласованное вложение под тем же префиксом; имя **`presentation_grammar`** в коде — `CascadeIdeSettings.PresentationGrammar`). |
| 109 | |
| 110 | **Что такое *screen* в имени `display.screens`:** в разговорном английском *screen* часто читается как «монитор ОС». Здесь **screen** — в том же духе, что **`screen_markers`** в [таблице токенов](#adr0017-presentation-grammar): **группа** в строке, граница одной презентационной поверхности пресета, кокпитная метафора PFD/MFD. Семантика «одна пара `( … )` ↔ один физический дисплей в конфигурации ОС» зафиксирована [выше](#adr0017-several-monitors); будущее имя таблицы **не** меняет парсер и семантику скобок **без** явной миграции кода. |
| 111 | |
| 112 | **Почему не `display.layout`:** слово **layout** уже перегружено — **CockpitPresentationLayout**, [0046](0046-presentation-layout-authority-and-cockpit-invariants.md), плюс раскладка **внутри** якоря (**instrument deck**, [0063](0063-instrument-deck-named-composition-one-anchor.md)). Отдельная таблица **`[display.layout]`** с ключом **`topology`** была бы читаемее без «экранов», но риск смешения с *внутренней* раскладкой региона выше, чем у **`display.screens`** после абзаца выше. Если продукт выберет **`layout`**, в доке и комментарии к TOML зафиксировать: *только многооконная / многоякорная топология пресета, не deck и не внутренняя сетка региона*. |
| 113 | |
| 114 | **Миграция:** читать старые ключи как **legacy** N релизов; при конфликте старого и нового — приоритет в реализации (новый канон побеждает при наличии); обновить примеры `settings.toml` при внедрении. Немедленная смена ключей в коде **не** требуется этим разделом — только зафиксированное направление; детали сроков — в задачах на загрузчик TOML. |
| 115 | |
| 116 | <a id="adr0017-presentation-grammar"></a> |
| 117 | **Где хранить и зачем не только `workspace.toml`:** раскладка по **физическим дисплеям** — **личная** история (у членов команды разное железо). Репозиторный **`.cascade/workspace.toml`** ([0021](0021-pfd-mfd-cockpit-attention-model.md) §2.1, [0028](0028-user-settings-toml-localappdata-and-secrets.md)) — про **соглашение команды** по панелям и зонам в общей модели внимания, **не** про обязательную для всех схему «три монитора». В **`settings.toml`** в **корне** задаются строки **`presentation`** и/или **`zone_screen_layout`**; **опциональные** токены грамматики (**`screen_markers`**, **`screen_separator`**, **`zone_separator`**, литералы якорей — см. таблицу ниже) — в секции **`[presentation_grammar]`**. Всё это задаёт **топологию именно под железо пользователя** — **прежде всего** в **`settings.toml`** (`%LocalAppData%\CascadeIDE\`, [0028](0028-user-settings-toml-localappdata-and-secrets.md)); при merge с бандлом `UiModes/` и overlay репо **эти** ключи берутся из **пользовательского** слоя и **перекрывают** одноимённые, если они когда-либо появятся в шипнутом или репозиторном `workspace.toml` (дефолт продукта, не «решение команды о мониторах»). |
| 118 | |
| 119 | **Грамматика строки `presentation` (настраивается в TOML):** в том же файле, что и **`presentation`** / **`zone_screen_layout`**, в секции **`[presentation_grammar]`** задаются **опциональные** ключи — **какие символы** считать маркерами экрана, разделителем экранов, разделителем якорей и **какими строками обозначать три зоны** (длинные или короткие — один ключ на зону). Значения **на усмотрение пользователя**; ниже — **дефолты**, с которыми совпадают примеры по всему этому ADR: |
| 120 | |
| 121 | | Ключ в TOML (внутри `[presentation_grammar]`) | Дефолт | Смысл | |
| 122 | |-------------|--------|--------| |
| 123 | | **`screen_markers`** | **`"()"`** | строка из **двух** символов: открывающий и закрывающий границу **одного** дисплея | |
| 124 | | **`screen_separator`** | **`" "`** | разделитель **между** группами (дисплеями), обычно пробел между `)` и следующим `(` | |
| 125 | | **`zone_separator`** | **`"+"`** | разделитель **якорей** внутри одной пары маркеров; **в строке `presentation` между якорями допускается только этот литерал** (не другой символ «для красоты») | |
| 126 | | **`pfd_zone_identifier`** | **`"PFD"`** | литерал якоря PFD в строке `presentation` (регистр и написание — как задано) | |
| 127 | | **`forward_zone_identifier`** | **`"Forward"`** | литерал якоря лобового экрана | |
| 128 | | **`mfd_zone_identifier`** | **`"MFD"`** | литерал якоря MFD | |
| 129 | |
| 130 | Три литерала должны быть **попарно различимы** (сравнение без учёта регистра). При коллизии реализация **сбрасывает** все три идентификатора на **дефолты** из таблицы. |
| 131 | |
| 132 | <a id="adr0017-anchor-aliases"></a> |
| 133 | **Идентификаторы якорей.** Пользователь может задать, например, **`forward_zone_identifier = "Lob"`** и писать **`(PFD+Lob+MFD)`**. Короткие буквы **P** / **F** / **M** — тем же ключом: **`pfd_zone_identifier = "P"`** и т.д.; тогда в строке только **`(P+F+M)`**, без отдельных ключей «алиас». |
| 134 | |
| 135 | Примеры при **дефолтных** идентификаторах (**`PFD`**, **`Forward`**, **`MFD`**): в строке — полные литералы, например **`(PFD+Forward+MFD)`**, **`(PFD) (Forward) (MFD)`**. Примеры с одной буквой на якорь — при явной настройке **`[presentation_grammar]`** с короткими **`pfd_zone_identifier`** / **`forward_zone_identifier`** / **`mfd_zone_identifier`**. |
| 136 | |
| 137 | <a id="adr0017-presentation-ebnf"></a> |
| 138 | **Грамматика (EBNF).** Ниже — **однозначное** описание; литералы якорей берутся из ключей TOML (см. таблицу). Пробелы **внутри** пары `screen_markers` между якорями не допускаются (только `zone_sep`). |
| 139 | |
| 140 | ```ebnf |
| 141 | (*--- Параметры из TOML: O, C, Z, S; идентификаторы якорей PfdId, ForwardId, MfdId (дефолты — см. таблицу) ---*) |
| 142 | |
| 143 | presentation ::= [ SP ] screen { SP screen } [ SP ] |
| 144 | screen ::= "(" anchor { zone_sep anchor } ")" |
| 145 | zone_sep ::= Z |
| 146 | anchor ::= pfd_zone_identifier | forward_zone_identifier | mfd_zone_identifier |
| 147 | |
| 148 | pfd_zone_identifier ::= (* литерал из ключа pfd_zone_identifier *) |
| 149 | forward_zone_identifier ::= (* литерал из ключа forward_zone_identifier *) |
| 150 | mfd_zone_identifier ::= (* литерал из ключа mfd_zone_identifier *) |
| 151 | |
| 152 | (* после разбора: семантика PFD / лобовое / MFD — [0021](0021-pfd-mfd-cockpit-attention-model.md) *) |
| 153 | |
| 154 | (* лексер: три строки якоря; совпадение в порядке убывания длины литерала; для литерала длины 1 — без учёта регистра *) |
| 155 | |
| 156 | SP ::= U+0020 { U+0020 } (* один или более пробелов — разделитель экранов при дефолте *) |
| 157 | ``` |
| 158 | |
| 159 | **Параметризация из TOML:** пусть **`O`** и **`C`** — первый и второй символ строки **`screen_markers`**, **`Z`** — строка **`zone_separator`**, **`S`** — строка **`screen_separator`**, литералы якорей — значения ключей **`pfd_zone_identifier`**, **`forward_zone_identifier`**, **`mfd_zone_identifier`**. Тогда: |
| 160 | |
| 161 | ```ebnf |
| 162 | presentation ::= [ S ] screen { S screen } [ S ] |
| 163 | screen ::= O anchor { Z anchor } C |
| 164 | (* anchor — как выше; токены якорей из TOML *) |
| 165 | ``` |
| 166 | |
| 167 | Между якорями в одном экране допускается **только** литерал **`Z`** из TOML (при дефолте — **`+`**). Парсер **не** подставляет **`|`**, **`+`** или другой символ вместо выбранного **`Z`**. Строка **`presentation`** должна использовать **те же** литералы, что и выбранные токены. Парсер читает сначала ключи грамматики из **`[presentation_grammar]`** (или дефолты), затем применяет соответствующую продукцию. В коде — свойство **`CascadeIdeSettings.PresentationGrammar`**; merge с бандлом — в реализации ([0010](0010-ui-modes-toml-configuration.md), [0028](0028-user-settings-toml-localappdata-and-secrets.md)). |
| 168 | |
| 169 | **В конфиге (например TOML):** то же компактное значение можно сопроводить **обычным комментарием** `# …` — кратко пояснить для себя якоря и число дисплеев; отдельные отсылки к ADR в пользовательском файле для этого **не обязательны**. |
| 170 | |
| 171 | **Продуктовый слой (вне этого ADR):** где и как давать пользователю развёрнутые объяснения (внешняя документация, справка в IDE, приоритеты) — решение **уровня продукта**, не ADR; см. [architecture-policy.md](../architecture-policy.md). |
| 172 | |
| 173 | **Три типовых раскладки по дисплеям:** |
| 174 | |
| 175 | | Схема | Смысл | |
| 176 | |--------|--------| |
| 177 | | **`(PFD+Forward+MFD)`** … (дефолтный **`Z`**); **`(PFD|Forward|MFD)`** … при **`zone_separator = "|"`**; **`(P+F+M)`** — если в **`[presentation_grammar]`** заданы короткие **`pfd_zone_identifier`** / **`forward_zone_identifier`** / **`mfd_zone_identifier`** | Один дисплей: три якоря в **одном** `TopLevel` (`MainGrid`). | |
| 178 | | **`(PFD+Forward) (MFD)`** …; **`(P+F) (M)`** — при тех же коротких идентификаторах в TOML | **Два `TopLevel`:** главное окно — **PFD и лобовое вместе**; второе окно — зона **MFD** (`MfdHostWindow`, вторичный контур). Типичный компромисс «два монитора». | |
| 179 | | **`(PFD) (Forward) (MFD)`** …; **`(P) (F) (M)`** — при коротких идентификаторах | **Три `TopLevel`:** по **одной зоне на окно** (PFD; лобовое; MFD). Идеал при трёх мониторах ([0021](0021-pfd-mfd-cockpit-attention-model.md)); см. расхождение с v1 в [«Состояние реализации»](#adr0017-implementation-status). | |
| 180 | |
| 181 | **Канон числа окон:** **`(P+F)(M)`** ⇔ **два** верхнеуровневых окна (главное держит P+F, M вынесен); **`(P)(F)(M)`** ⇔ **три** верхнеуровневых окна (P, F, M раздельно). Это **семантика строки `presentation`**, не «одно окно на три экрана ОС» и не три окна на одном мониторе как цель по умолчанию. |
| 182 | |
| 183 | **Пространственный канон (три дисплея, типичный ряд):** **слева направо** на экранах (в порядке `PresentationMonitorTopology.OrderScreensForPresentation`: слева направо, затем сверху вниз) идут группы **`(…) (…) (…)`** в **том же порядке, в каком они записаны в строке `presentation`**. Первая группа — левый экран в этом порядке, вторая — средний, третья — правый. Примеры: **`(P) (F) (M)`** — слева PFD, по центру Forward, справа MFD; **`(M) (F) (P)`** — слева MFD, по центру Forward, справа PFD. Фиксированной привязки «P всегда слева» нет — задаёт **только** порядок скобок. Если физическая конфигурация мониторов не в один ряд, пользователь сопоставляет **i-ю** группу **i-му** экрану в выбранном порядке (или подгоняет раскладку ОС). |
| 184 | |
| 185 | <a id="adr0017-zone-weights"></a> |
| 186 | |
| 187 | ### Доли зон на одном дисплее (опциональные веса у якорей) |
| 188 | |
| 189 | **Принято:** внутри **одной** пары **`screen_markers`** (один физический дисплей), если якорей **больше одного**, допускаются **опциональные положительные коэффициенты** — **литералы вещественных чисел** **перед** литералом якоря (в канонической записи **без** пробела между числом и идентификатором: **`0.25P`**). Перед разбором парсер **удаляет все символы Unicode whitespace** внутри пары скобок экрана, поэтому удобочитаемые варианты вроде **`(0.25P + 0.75F)(M)`** эквивалентны **`(0.25P+0.75F)(M)`**. Смысл: **доля** ширины основной полосы колонок (типичный случай — три колонки в `MainGrid`), сумма коэффициентов по **всем якорям этой группы** = **1**. Запись читается естественно, например **`(0.25P+0.75F)(M)`** — на первом экране P и F делят ширину в пропорции 1∶3; на втором экране один якорь **M** на весь дисплей, **отдельные веса не задаются** (и **между** группами **`(…) (…)`** весов нет: граница между мониторами — стол/геометрия ОС, не доля в строке). |
| 190 | |
| 191 | **Инвариант:** коэффициенты меняют только **доли** внутри одной группы (одного экрана). **Топология** — сколько групп `(…)`, какие якоря на каком экране, сколько физических дисплеев, и продуктовые следствия вроде максимизации главного окна при старте — задаются **составом якорей и скобок**, а не числами; подбор **`0.25` vs `0.5`** не переключает «два монитора на три» и не меняет семантику «на первом экране есть P и F». |
| 192 | |
| 193 | **Когда веса не указываются** — поведение как сейчас: равные доли между якорями в группе или существующий дефолт сетки (реализация). |
| 194 | |
| 195 | **Внутри одной группы** с **двумя и более** якорями: либо у **каждого** якоря задан коэффициент и **сумма = 1**, либо **ни у одного** (равные доли). Смешение вроде **`(0.25P+F+M)`** — **ошибка разбора** (неоднозначно). |
| 196 | |
| 197 | **Одна зона в скобках** — только литерал якоря, без коэффициента (или эквивалентно один якорь на весь экран). |
| 198 | |
| 199 | **Формат числа:** десятичная точка **`.`** (как в TOML/JSON); локаль с запятой в строке `presentation` **не** поддерживается, чтобы не дублировать неоднозначность. |
| 200 | |
| 201 | **Связь с парсером:** `PresentationParser` разбирает опциональные префиксы весов; внутри одной пары `screen_markers` структура списка якорей задаётся грамматикой **Eto.Parse** (`PresentationInnerEtoGrammar`, пакет NuGet **Eto.Parse**). Результат — `PresentationParseResult.Screens` как списки **`PresentationAnchorSlot`** (`Kind` + `Weight?`). Строки **без** коэффициентов по-прежнему каноничны (`Weight == null` на всех якорях группы). |
| 202 | |
| 203 | Ниже — **расширение EBNF** относительно [базовой продукции](#adr0017-presentation-ebnf); при желании объединить с основным блоком EBNF выше в одну продукцию (документация). |
| 204 | |
| 205 | ```ebnf |
| 206 | (*--- Дополнение: веса только внутри screen с двумя и более якорями; сумма weight = 1 ---*) |
| 207 | |
| 208 | weight ::= (* положительный литерал: целое или десятичное, десятичная точка U+002E *) |
| 209 | weighted_anchor ::= weight anchor | anchor |
| 210 | screen ::= O weighted_anchor { Z weighted_anchor } C |
| 211 | (* семантика: в одном screen либо каждый weighted_anchor — это «weight anchor», либо каждый — просто «anchor»; смешение запрещено; при весах sum=1 *) |
| 212 | |
| 213 | (* Базовая форма без весов — как сейчас: только anchor *) |
| 214 | ``` |
| 215 | |
| 216 | **Примеры** (короткие идентификаторы **`P`**, **`F`**, **`M`** в **`[presentation_grammar]`**): **`(0.25P+0.75F)(M)`**; **`(0.2P+0.3F+0.5M)`**; **`(P+F+M)`** — без весов, три равные доли. |
| 217 | |
| 218 | <a id="adr0017-nested-vh-notation"></a> |
| 219 | |
| 220 | ### Вложенные оси **v** и **h** (T-layout; пояснение для агентов и обзорной документации) |
| 221 | |
| 222 | Иногда раскладку на **одном** физическом экране описывают **формулой с явными осями**, например: |
| 223 | |
| 224 | **`0.3vPFD + 0.7v(0.8hForward + 0.2hMFD)`** |
| 225 | |
| 226 | **Расшифровка (смысл для человека и для LLM):** |
| 227 | |
| 228 | 1. Сначала делим экран **вертикально** (**`v`**): **30%** ширины слева — зона **PFD** (контекст workspace, дерево связей, первичные индикаторы в модели кокпита). |
| 229 | 2. Оставшиеся **70%** справа — отдельная вертикальная «полоса»; внутри неё делим **горизонтально** (**`h`**): **верхние 80%** — **Forward** (лобовое стекло: редактор, объект работы), **нижние 20%** — **MFD** (телеметрия, вторичный контур, настройки «под рукой»). |
| 230 | |
| 231 | Это классический **T-layout** кокпита: главное (код и статус «полёта») — в центре поля зрения, вспомогательное — внизу. Без указания **`v`** / **`h`** и порядка вложенности анализатор или агент **не могут однозначно** сопоставить доли с **`RowDefinitions` / `ColumnDefinitions`** в Avalonia: сначала нужно знать, **какой сплит идёт первым** (здесь — вертикальный 0.3/0.7), затем — как делится **правая** часть по **строкам** (0.8/0.2). |
| 232 | |
| 233 | **Связь с текущей строкой `presentation` (реализация v1):** парсер и веса в [§ «Доли зон»](#adr0017-zone-weights) сейчас задают **одну ось** — типично **три колонки** `MainGrid` (**PFD | Forward | MFD**) с долями по ширине. **Вложенная** запись **`v` / `h`** в этом ADR — **целевая семантика** для будущего расширения грамматики и/или отдельного слоя композиции (см. [0036](0036-cds-channel-compositor-surface-pipeline.md)); до появления парсера с вложенными группами её **нельзя** считать эквивалентом одной плоской суммы **`(0.3P+0.7F+…)`** без отдельной спецификации. |
| 234 | |
| 235 | <a id="adr0017-weight-fuse-policy"></a> |
| 236 | |
| 237 | **Предохранитель (политика стабильности геометрии):** если веса и оси заданы **явно в конфиге**, продукт **не** должен **динамически пересчитывать** пропорции в рантайме «по удобству» (изменение размера окна не должно подменять доли из настроек). |
| 238 | |
| 239 | - **Анализатор** (разбор / валидация конфигурации): в **каждой группе вложенности** сумма коэффициентов на этой оси **= 1** (как уже для плоского экрана с весами у якорей). |
| 240 | - **Surface compositor** (конвейер [0036](0036-cds-channel-compositor-surface-pipeline.md)): при старте сессии **жёстко фиксирует** вычисленные из конфига **нормализованные** доли (и при необходимости пиксельные границы после измерения), без последующего «плавающего» пересчёта долей при обычном ресайзе; исключения (если появятся) — отдельное явное правило продукта, не молчаливое изменение коэффициентов. |
| 241 | |
| 242 | **Идеал (три физических дисплея):** **`(PFD) (Forward) (MFD)`** — целевая кокпитная раскладка при достаточном железе: контекст workspace / обозреватель и первичные индикаторы на первом экране, объект работы (редактор) на втором, вторичные тяжёлые панели (чат, терминал, trace…) на третьем. **По канону** пресет с **тремя группами** `(…) (…) (…)` означает **три отдельных `TopLevel`** (см. таблицу «Три типовых раскладки» выше), а не одно окно на три монитора без разбиения зон. |
| 243 | |
| 244 | **Метафора внимания (как в кокпите):** помимо **лобового** поля зрения есть **боковые зоны** — их можно подхватить **одним взглядом** (периферийное внимание), не отрываясь от работы «вперёд» надолго. **PFD** и **MFD** в этом смысле — **боковые** относительно **лобового (Forward)**, а не «второстепенные экраны»; мультиоконность и якорь направлений от экрана лобового согласуются с этой моделью. |
| 245 | |
| 246 | **Типичный компромисс (два дисплея):** **`(PFD+Forward) (MFD)`** при дефолтном **`Z`**, либо **`(PFD|Forward) (MFD)`** при **`zone_separator = "|"`** — первый монитор: PFD и лобовое вместе (как сейчас в одном `MainGrid`); второй — зона **MFD**. Удобно и распространённо; от идеала «три экрана» отличается объединением PFD и лобового на одном дисплее. |
| 247 | |
| 248 | ### Матрица инстансов `SkiaHost` (зафиксировано для v1) |
| 249 | |
| 250 | `SkiaHost` считается **на слот**, а не «один на всё окно». Общее число рабочих поверхностей в типовых топологиях — три (PFD / Forward / MFD), меняется только распределение между `TopLevel`. |
| 251 | |
| 252 | | Топология (формула `presentation`) | `MainWindow` | `MfdHostWindow` | Итого | |
| 253 | |--------|--------|--------|--------| |
| 254 | | **`(PFD+Forward+MFD)`** | `SkiaHost(PFD)`, `SkiaHost(Forward)`, `SkiaHost(MFD)` | — | **3** слота / **1** `TopLevel` | |
| 255 | | **`(PFD+Forward) (MFD)`** / **`(P+F) (M)`** | `SkiaHost(PFD)`, `SkiaHost(Forward)` | `SkiaHost(MFD)` | **3** слота / **2** `TopLevel` (канон) | |
| 256 | | **`(PFD) (Forward) (MFD)`** / **`(P) (F) (M)`** | по канону — только один якорь на окно (отдельные хосты PFD и Forward — roadmap) | `SkiaHost(MFD)` | **3** слота / **3** `TopLevel` (канон); **v1:** как строка выше, т.к. P+F в `MainWindow` | |
| 257 | |
| 258 | **Следствие:** дополнительный «агрегирующий» `SkiaHost` на весь `MainWindow` не требуется; базовая геометрия задаётся `presentation`/`MainGridColumnDefinitions`, а поверхности остаются слот-ориентированными. |
| 259 | |
| 260 | **Четвёртый и далее** дисплей — опционально (например полноэкранная телеметрия, второй контур Mfd, внешний браузер доков — по пресету и политике продукта). |
| 261 | |
| 262 | Не единственная возможная схема; пресеты задают соответствия дисплей↔зона. **Канон** числа `TopLevel` для строки `presentation` — в таблице «Три типовых раскладки» и в матрице `SkiaHost` выше; факт кода для тройной группы — в [«Состояние реализации»](#adr0017-implementation-status). Снимок MCP для нескольких окон уже поддерживается ([п. 7](#adr0017-p7)), при новых сценариях — уточнение полей/ролей в той же поставке. |
| 263 | |
| 264 | ## Решение (принципы; детализация — по итогам обсуждения) |
| 265 | |
| 266 | <a id="adr0017-p1"></a> |
| 267 | 1. **Один процесс, несколько `TopLevel`.** Окна принадлежат одному экземпляру IDE; общее состояние решения, агента, настроек — **единый** граф VM/сервисов за фасадом, без второго «независимого» экземпляра приложения. *Исключения* (если когда-нибудь понадобятся) — отдельное решение и отдельный ADR. |
| 268 | |
| 269 | <a id="adr0017-p2"></a> |
| 270 | 2. **Главный фрейм.** Сохраняется понятие **основного окна** (редактор, решение, главная навигация по умолчанию). Дополнительные `TopLevel` — это в первую очередь **носители вынесенных зон** (см. контекст: MFD/PFD на **втором или третьем мониторе**); они **не** обязаны дублировать полный кокпит. Состав и привязка к дисплею — пресет режима и/или явное действие пользователя (например «вынести MFD» / «вынести страницу чата» (как часть вторичного контура в зоне Mfd)). Роль `mfd_host` в снимке MCP относится к окну-хосту зоны Mfd и не ограничивает продукт только «чатом». Закрытие главного окна — **политика выхода из приложения** (закрыть все дочерние или запрос подтверждения) — фиксируется в реализации, не оставлять неявным. |
| 271 | |
| 272 | <a id="adr0017-p3"></a> |
| 273 | 3. **Что может жить во втором и следующих окнах (кандидаты, не обязательно всё в v1):** прежде всего **регион зоны Mfd** на **отдельном физическом дисплее** — в терминах [0021](0021-pfd-mfd-cockpit-attention-model.md) это семантика вторичного внимания; в коде тот же **хост** `MfdShellView`, внутри которого переключаются **страницы** `SecondaryShellPage` (чат, терминал, сборка, …) — **страница не является «зоной»**, зона — **Mfd** как якорь внимания; при необходимости **PFD** или компактная полоса подтверждений/критичных индикаторов на другом мониторе; выносимые фрагменты хрома в смысле [0012](0012-floating-workspace-chrome.md). **Документы редактора как floating MDI** остаются **вне объёма этого ADR**, пока не будет отдельного решения (как в [0012](0012-floating-workspace-chrome.md)). |
| 274 | |
| 275 | <a id="adr0017-p4"></a> |
| 276 | 4. **Связь с режимами UI ([0010](0010-ui-modes-toml-configuration.md)).** Пресеты задают **видимость и слоты** поверх доступных поверхностей: одно окно или несколько. **Топология презентации** зон (`AttentionLayoutSurfaceKind`: одно окно / несколько `TopLevel` и т.д.) согласуется с merge слоёв [0010](0010-ui-modes-toml-configuration.md) и отделением от карты панель→зона — см. подраздел «Топология презентации зон» в [0010](0010-ui-modes-toml-configuration.md). **Строка раскладки по дисплеям** — **`presentation`** (короткое имя) **или** **`zone_screen_layout`**; **одно** из двух, не оба. Значение — **литерал** из «Несколько мониторов» (например `(PFD+Forward) (MFD)`); рядом **опционально** токены грамматики из [таблицы](#adr0017-presentation-grammar) (**`screen_markers`**, **`screen_separator`**, **`zone_separator`**, литералы якорей) ([EBNF](#adr0017-presentation-ebnf)). **Хранение:** прежде всего **`settings.toml`** пользователя ([§ выше](#adr0017-presentation-grammar), [0028](0028-user-settings-toml-localappdata-and-secrets.md)), чтобы не смешивать **личную** схему мониторов с **командным** репозиторным `workspace.toml`. Расширение: ключи уровня «какие панели во вынесенной зоне по умолчанию» — **в TOML режима** / Flight при необходимости. Валидация и enum — в реализации. |
| 277 | |
| 278 | <a id="adr0017-p5"></a> |
| 279 | 5. **Сохранение геометрии.** Позиции и размеры окон **вынесенных зон** (в т.ч. на 2-м/3-м мониторе) — **не** обратная запись в шипнутые `UiModes/*.toml` (правило [0010](0010-ui-modes-toml-configuration.md)). **В коде v1:** геометрия **`MfdHostWindow`** персистится в пользовательском **`settings.toml`** (`MfdHostWindowPixelX` / `PixelY` / `Width` / `Height` — см. [«Состояние реализации»](#adr0017-implementation-status)); иные окна / полный snapshot workspace — при появлении отдельной договорённости. |
| 280 | |
| 281 | **Пресеты и выбор дисплея для вынесенной зоны (принятое направление):** помимо сырого идентификатора дисплея ОС и сохранённых прямоугольников допускается **семантика, естественная пользователю** — направление соседства относительно **экрана, на котором в текущей раскладке представлена зона лобового (forward)** ([0021](0021-pfd-mfd-cockpit-attention-model.md)), а не от **primary** монитора ОС по умолчанию. **Кроссплатформенно:** целевой стек — **.NET + Avalonia**; перечисление дисплеев и их **границы в общей координатной сетке** (по ним вычисляется сосед в духе `left | right | up | down`) — через API графической платформы на **Windows, Linux и macOS**; это **не** привязка к одной ОС. Различия между ОС (имена дисплеев, hotplug, Wayland vs X11 и т.д.) — **в реализации и тестах**, не в продуктовой модели якоря forward. Имена ключей и схема в данных — в той же поставке, что и мультиоконность / [0010](0010-ui-modes-toml-configuration.md). **Tie-break:** если в выбранном направлении **несколько** соседних дисплеев, **не** навязывать автоматический выбор «лучшего» — **на усмотрение пользователя** (явный выбор экрана в UI, уточнение пресета, либо переход на сырой идентификатор/сохранённый прямоугольник). **Лобовое, разнесённое на несколько физических дисплеев** — редкий случай; для **v1** достаточно **одного экрана якоря** для forward; обобщение — при явной потребности. При смене конфигурации мониторов — **fallback** (сохранённая геометрия, возврат в главное окно и т.д.) остаётся предметом реализации. |
| 282 | |
| 283 | <a id="adr0017-p5-primary-vs-forward"></a> |
| 284 | **Основной дисплей ОС и якорь Forward (принято):** системное понятие **«основной / primary»** монитора (в Windows — «сделать основным дисплеем») **не отождествляется** с семантикой **лобового экрана (Forward)** в модели кокпита. Primary нужен ОС и драйверам (расположение панели задач, масштаб и т.п.); типичный **конфликт** — сенсорный монитор, который **должен** быть основным из‑за калибровки касания, хотя в рабочей раскладке пользователя лобовым по смыслу является **другой** дисплей. Считать Forward по умолчанию равным primary **опасно** — ломает такие конфигурации. **Согласование** физической расстановки мониторов, их порядка в настройках ОС и строки **`presentation`** — **ответственность пользователя**; продукт не подменяет расстановку стола и не выводит семантику якорей только из флага primary. Реализация сопоставляет группы `(…) (…) …` с дисплеями по принятой схеме (геометрия рабочих областей и порядок в строке — см. код), без тождества «первый в `presentation` = primary ОС». |
| 285 | |
| 286 | <a id="adr0017-p6"></a> |
| 287 | 6. **Агент (ACP, [0016](0016-agent-client-protocol-external-agent.md)).** Транспорт к внешнему агенту **не зависит** от числа окон: один канал stdio/сессия. Решение этого ADR — только **куда монтируется** встроенный UI ответов и подтверждений (главное окно vs дополнительное). Краткие подтверждения и критичные сигналы по-прежнему должны быть доступны в **PFD-зоне** внимания (концепт §3); при втором окне — либо дубль компактной полосы, либо явное правило «фокус на главном окне для подтверждения». |
| 288 | |
| 289 | <a id="adr0017-p6-confirmations"></a> |
| 290 | **Подтверждения при нескольких `TopLevel` (принятое направление):** не опираться только на системную модалку поверх «не того» окна. Показ запроса — в **потоке внимания PFD** (баннер/строка; презентация может быть без классической модалки на весь экран). Опционально **вторичный канал внимания** (звук, мигание в области уведомлений ОС), если фокус на MFD/втором окне. **Ответ пользователя** — те же семантики, что и у кнопок: **палитра команд** и **жесты из [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md)** — шипнутый [`Hotkeys/hotkeys.toml`](../../Hotkeys/hotkeys.toml) и пользовательский оверлей `%LocalAppData%\CascadeIDE\hotkeys.toml` ([0028](0028-user-settings-toml-localappdata-and-secrets.md)). До отдельного решения по протоколу: один вызов `request_confirmation` (и аналоги) по-прежнему завершается **ok/cancel** после ответа; «неблокирующее» относится к **UX презентации**, не к обязательной смене модели MCP. |
| 291 | |
| 292 | <a id="adr0017-p7"></a> |
| 293 | 7. **MCP и паритет ([0002](0002-debug-human-agent-parity.md), [0008](0008-mcp-contracts-and-testable-infrastructure.md), [0012](0012-floating-workspace-chrome.md)).** **Уже реализовано:** снимок UI для агента/тестов не ограничен одним `MainWindow` — `ide_get_ui_layout` строит **`windows[]`** по всем открытым `Window` процесса; действия по имени контрола учитывают окна помимо главного (см. контекст выше). **При появлении новых продуктовых окон или новой семантики** (например выделенный второй `TopLevel` под MFD с отдельной таксономией зон) **точечно расширять** контракт в **той же** поставке: новые значения `role`, поля идентификации региона/поверхности, тесты паритета — по аналогии с [0012](0012-floating-workspace-chrome.md). Мультиоконные поверхности не должны оставаться «только для человека» без осознанного исключения. |
| 294 | |
| 295 | <a id="adr0017-p8-mfd-host-wide"></a> |
| 296 | 8. **`MfdHostWindow` — только полный вторичный контур.** Для раскладки вроде **`(PFD+Forward) (MFD)`** / **`(PFD|Forward) (MFD)`** второй дисплей семантически несёт **всю зону Mfd**: одно окно **`MfdHostWindow`** с **полным** **`MfdShellView`** — тем же хостом **страниц** (`SecondaryShellPage`: чат, терминал, сборка, обозреватель решения, …) и тем же `DataContext` (`MainWindowViewModel`), что и **вторичный контур** в главном окне. **Паритет** здесь — совпадение **семантики вторичного контура** (набор страниц внутри `MfdShellView`), а **не** обязательное воспроизведение **всей** визуальной колонки под якорем Mfd в `MainWindow`: в главном окне под якорем Mfd — **один** `MfdShellView`; инструменты вторичного внимания (включая обозреватель решения) переключаются **страницами**, а не отдельным зашитым сплитом «дерево + shell» ([0021](0021-pfd-mfd-cockpit-attention-model.md): **зона** внимания ≠ **страница**). Отдельный `TopLevel` **не обязан** дублировать прочие панели главного окна вне семантики вторичного контура. **Не** делаем отдельного продукта «второе окно только с одной страницей» (узкий хост); альтернатива «только чат» **отклонена** как целевое состояние. Плейсмент, первичное сохранение геометрии и привязка к индексу экрана из `presentation` — см. [«Состояние реализации»](#adr0017-implementation-status); открыты: доводка fallback при смене мониторов, продуктовый UX ([п. 5](#adr0017-p5)). |
| 297 | |
| 298 | ## Последствия |
| 299 | |
| 300 | - Потребуется **дизайн владения VM**: общие сервисы, дочерние окна как представления или отдельные лёгкие VM с подпиской на общий сессионный слой. |
| 301 | - Тесты UI и сценарии, которые жёстко предполагали **только** дерево `MainWindow` без учёта `windows[]` в MCP, потребуют обновления или явного двойного режима (одно окно / несколько окон); сам MCP-снимок по нескольким `TopLevel` уже поддерживается. |
| 302 | - Документация для пользователя (позже): как вынести панель, как вернуть, где сохраняется геометрия. |
| 303 | |
| 304 | ## Отклонённые альтернативы (как целевое состояние) |
| 305 | |
| 306 | - **Полагаться только на внешний Cursor/IDE на втором мониторе** без встроенной мультиоконности — отклонено как не закрывающее сценарий «одна Cascade + зоны экрана» и PFD/MFD внутри продукта (см. концепт §8 — внешние инструменты **дополняют**, не заменяют осмысленную раскладку Cascade). |
| 307 | - **Сразу вводить полный MDI документов** — вне объёма; см. [0012](0012-floating-workspace-chrome.md). |
| 308 | - **Несколько окон на одном мониторе ради «запрета перетаскивания средствами ОС»** — отклонено как мотивация: такая цель слаба и закрывается раскладкой **внутри одного окна** (см. подраздел «Один монитор и три семантические зоны» выше). |
| 309 | |
| 310 | <a id="adr0017-modes-scope"></a> |
| 311 | |
| 312 | ## Уточнение: режимы UI (Power, Flight и др.) |
| 313 | |
| 314 | **Принято:** переработка **Power**, **Balanced** и остальных пресетов/семей в смысле [0010](0010-ui-modes-toml-configuration.md) — **отдельная** линия работ; в объёме **первой поставки** второго `TopLevel` эти режимы **не трогаем**; последующая переработка пресетов и семей — **отдельная** линия. Мультиоконность v1 **не** блокируется и **не** смешивается с согласованием «как будет в Power». Вопрос «только **Flight** / **Power** с флагом» для мультиоконности **снят** до отдельной дорожной карты режимов. **Flight** по-прежнему удобен как полигон для экспериментов (см. контекст выше), без обязательства ограничивать продукт только им. |
| 315 | |
| 316 | <a id="adr0017-open-questions"></a> |
| 317 | ## Открытые вопросы (для обсуждения перед кодом) |
| 318 | |
| 319 | - **Три `TopLevel` на одном физическом мониторе** (три якоря PFD/лобовое/MFD как три рамки ОС): **по умолчанию не цель** — достаточно связки «главное окно + вынесенная **зона** на другом мониторе» (2-й/3-й дисплей), мультимонитора по [0021 §13](0021-pfd-mfd-cockpit-attention-model.md) и оси **одно окно + пресет/FancyZones** в [0021](0021-pfd-mfd-cockpit-attention-model.md). Отдельный пересмотр «три на одном» — только при явной обратной связи (например ультраширокий один дисплей). |
| 320 | - Второй `TopLevel` под зону Mfd на отдельном мониторе — см. [п. 8](#adr0017-p8-mfd-host-wide) и [«Состояние реализации»](#adr0017-implementation-status): **полный** `MfdShellView` в **`MfdHostWindow`**; семантика зон и страниц — [0021](0021-pfd-mfd-cockpit-attention-model.md). **Базовый** плейсмент, сохранение bounds и автозапуск — в коде. **Открыто:** EICAS/IDE Health при вынесении, **UX чата и терминала** (плотность, клавиатура), полнота fallback при смене мониторов; переработка режимов UI — см. [«Уточнение: режимы UI»](#adr0017-modes-scope). |
| 321 | - **Подтверждения агента (детали продукта):** таймауты, деструктивные операции, нужен ли отдельный модальный слой процесса — при реализации поверх принятого направления ([п. 6](#adr0017-p6) выше). |
| 322 | - **Пресеты и мониторы (детали реализации):** принятый ориентир — [см. п. 5](#adr0017-p5) выше (якорь **forward**, соседство `left | right | up | down`). **Tie-break** при нескольких соседях в одном направлении — **на стороне пользователя** ([см. п. 5](#adr0017-p5)), не обязательная эвристика в продукте. **Ключи раскладки по дисплеям** — **`presentation`** / **`zone_screen_layout`** и токены грамматики — **персональные**, см. [п. 4](#adr0017-p4) и [§ слой хранения](#adr0017-presentation-grammar); **открытая деталь реализации:** полнота fallback при несовпадении раскладки ОС. |
| 323 | |
| 324 | ## Принятие |
| 325 | |
| 326 | **2026-04-11** — статус **Accepted**; ссылка из [architecture-policy.md](../architecture-policy.md). [concept-to-implementation-map-v1.md](../ui-ux/concept-to-implementation-map-v1.md) §6 — мультиоконность и `MfdHostWindow` (актуальная привязка к файлам). **2026-04-11** — раздел [«Состояние реализации»](#adr0017-implementation-status): синхронизация ADR с кодом (топология, плейсмент, персистентность bounds). |
| 327 | |
| 328 | --- |
| 329 | |
| 330 | ## История изменений |
| 331 | |
| 332 | <a id="adr0017-history"></a> |
| 333 | |
| 334 | | Дата | Изменение | |
| 335 | |------|-----------| |
| 336 | | 2026-04-08 | [0021](0021-pfd-mfd-cockpit-attention-model.md), [0010](0010-ui-modes-toml-configuration.md); отклонённая мотивация «запрет перетаскивания ОС». | |
| 337 | | 2026-04-11 | **[доли зон внутри одного дисплея](#adr0017-zone-weights)** (опциональные коэффициенты у якорей; между группами `(…) (…)` весов нет). | |
| 338 | | 2026-04-11 | **[п. 5 доп. primary vs Forward](#adr0017-p5-primary-vs-forward)**. | |
| 339 | | 2026-04-11 | **[п. 8](#adr0017-p8-mfd-host-wide):** `MfdHostWindow` — только полный `MfdShellView` (все страницы); узкий одностраничный хост **не** делаем. | |
| 340 | | 2026-04-11 | **`(PFD+Forward) (MFD)` и аналоги:** второй экран = полный MFD. | |
| 341 | | 2026-04-11 | **`presentation`** / **`zone_screen_layout`:** прежде всего **`settings.toml`** ([0028](0028-user-settings-toml-localappdata-and-secrets.md)), не командный репо — см. [§ слой хранения](#adr0017-presentation-grammar), [п. 4](#adr0017-p4), [0010](0010-ui-modes-toml-configuration.md). | |
| 342 | | 2026-04-11 | **`zone_separator` строгий:** парсер не подставляет **`\|`** вместо **`+`** и наоборот — см. подраздел «Несколько мониторов», [EBNF](#adr0017-presentation-ebnf). | |
| 343 | | 2026-04-11 | **без отдельных `*_zone_alias`:** один литерал на зону — только **`pfd_zone_identifier`** / **`forward_zone_identifier`** / **`mfd_zone_identifier`**; короткие имена — тем же ключом (например **`pfd_zone_identifier = "P"`**). | |
| 344 | | 2026-04-11 | **грамматика `presentation`:** EBNF в [§](#adr0017-presentation-ebnf); ключи TOML — [§](#adr0017-presentation-grammar). | |
| 345 | | 2026-04-11 | **идеальная мультимониторная раскладка:** **`(PFD) (Forward) (MFD)`** (три дисплея); метафора **боковых зон кокпита** (лобовое + боковые поля одним взглядом); компромисс «два монитора»; окно-хост зоны Mfd (`MfdHostWindow`), роль в MCP `mfd_host`; **три `TopLevel` на одном мониторе** — не цель по умолчанию; **v1 / `MfdShellView`** уточнён по коду (`MainWindow.axaml`, `SecondaryShellPage`); MCP `windows[]` ([п. 7](#adr0017-p7)); подтверждения агента ([0030](0030-command-ids-hotkeys-and-ui-registry-layers.md)); **пресеты и второй дисплей:** якорь направлений — **экран forward (лобовое)**, не primary ОС; соседство `left \| right \| up \| down`; дисплеи — **кроссплатформенно** (.NET + Avalonia: Windows, Linux, macOS); лобовое на нескольких дисплеях — вне v1; **tie-break** (несколько соседей в одном направлении) — **на усмотрение пользователя**, не автоэвристика; | |
| 346 | | 2026-04-11 | **литералы якорей в TOML** (`pfd_zone_identifier`, …) — [§ таблица](#adr0017-presentation-grammar), [EBNF](#adr0017-presentation-ebnf). | |
| 347 | | 2026-04-11 | **обозначения дисплеев:** разделитель якорей внутри скобок — литерал **`zone_separator`** (**`Z`**, по умолчанию **`+`**); в прозе **`+`** и **`\|`** иллюстрируют один смысл, в строке `presentation` — только выбранный **`Z`**; несколько пар `(…) (…) (…)` — несколько дисплеев — см. «Несколько мониторов»; в TOML — пояснение в `#` без обязательных отсылок к ADR; продуктовая документация — [architecture-policy.md](../architecture-policy.md). | |
| 348 | | 2026-04-11 | **режимы UI (Power и др.):** не в scope первой поставки второго `TopLevel`; переработка режимов — отдельно; вопрос Flight vs Power для мультиоконности снят до той линии. | |
| 349 | | 2026-04-11 | **статус:** разделение «уже реализовано» / «дорожная карта» (парсер и TOML — не backlog). | |
| 350 | | 2026-04-11 | **терминология:** зона внимания **Mfd** vs **страницы** `SecondaryShellPage` во `MfdShellView` (чат — страница, не зона). | |
| 351 | | 2026-04-11 | **токены грамматики** в **`settings.toml`** — секция **`[presentation_grammar]`** (модель **`CascadeIdeSettings.PresentationGrammar`**). | |
| 352 | | 2026-04-11 | статус **Accepted**; навигатор [architecture-policy.md](../architecture-policy.md). | |
| 353 | | 2026-04-16 | [skia-surfaces-vs-overlays-v1.md](../design/skia-surfaces-vs-overlays-v1.md) (Skia-поверхности vs оверлеи, отказ от зашитых сплитов в пользу зон презентации); [п. 8](#adr0017-p8-mfd-host-wide) уточнён: обозреватель решения — страница вторичного контура, не отдельный сплит в колонке Mfd `MainWindow`. | |
| 354 | | 2026-04-17 | **Канон числа `TopLevel`:** `(P+F)(M)` ⇒ два окна; `(P)(F)(M)` ⇒ три; слева направо = порядок групп в строке; плейсмент хостов — только `PresentationHostWindowPlacement`. | |
| 355 | | 2026-04-18 | [§ `display.screens` / topology](#adr0017-display-screens-topology-naming); согласование с [0063](0063-instrument-deck-named-composition-one-anchor.md). | |