Перейти к содержанию

ADR 0017: Несколько окон приложения (мультиоконность), зоны экрана и поверхности агента

Статус: Accepted · Implemented
Дата: 2026-04-05
Обновлено: 2026-04-18 — канон числа TopLevel и display.screens / topology; подробности — § История.

Связанные ADR

ADR Роль
0010 режимы и TOML поверх каркаса окна; топология презентации зон — отдельный подраздел в 0010, не смешивать с attention_zone_panels
0012 плавающий хром; отдельные окна как одна из механик
0021 семантика зон PFD / лобовое / MFD — не синоним числа окон
0025 зоны в SDK
0016 внешний агент по ACP — ортогонально тому, в скольких окнах показывается встроенный UI
0030 идентификаторы команд, Hotkeys/hotkeys.toml + пользовательский оверлей, палитра
0031 пакеты уточнений в чате, не путать с PFD-подтверждениями
0002 паритет MCP
0008 контракты MCP
0063 ось instrument deck и слово presentation vs CDS — см. § там; ключи топологии — ниже в этом ADR
0120 primary_work_surface — Intercom или Editor в Forward (ортогонально presentation)

Вне ADR

Документ Роль
attention-zone-panel-playbook-v1.md зона ↔ панель ↔ топология; в коде — AttentionLayoutSurfaceKind
concept-pfd-mfd-cascade-v1.md UX-концепт PFD/MFD (superseded текстом 0021)
concept-to-implementation-map-v1.md карта концепт → код
skia-surfaces-vs-overlays-v1.md Skia-поверхности vs оверлеи

Резюме

  • Несколько TopLevel — продуктовая модель разнесения зон по мониторам; семантика зон (0021) не равна числу окон.
  • Источник истины раскладки — строка presentation / zone_screen_layout в settings.toml (0028), парсер + [presentation_grammar].
  • (P+F)(M) → два окна; (P)(F)(M) → три; порядок групп в строке = слева направо на экранах.
  • MfdHostWindow / PfdHostWindow — полный shell зоны; плейсмент только через PresentationHostWindowPlacement.
  • MCP ide_get_ui_layout уже отдаёт все окна; roadmap — EICAS на втором экране, fallbacks, подтверждения агента («Открытые вопросы»).

Состояние реализации (актуально по репозиторию)

Ниже — что уже есть в коде, чтобы не путать с продуктовым backlog из «Открытых вопросов».

Уже реализовано

Область Где в коде / что делает
Разбор строки presentation Services/Presentation/PresentationParser.cs, PresentationInnerEtoGrammar (Eto.Parse), PresentationGrammarTokens, PresentationLayoutAnalyzer, PresentationAnchorKind, PresentationAnchorSlot (веса); тесты CascadeIDE.Tests/PresentationParserTests.cs. Веса зон внутри группы — §. Колонки главного окна: PresentationMainGridColumnDefinitions.GetMainGridColumnDefinitions у MainWindowViewModel, применение в MainWindow.PresentationLayout.axaml.cs (ColumnDefinitions.Parse — Avalonia не биндит строку к ColumnDefinitions из VM); хвост MFD при двухякорном пресете — 340 или 0 при скрытии колонки под хост (реализация).
Топология ↔ дисплеи PresentationMonitorTopology.OrderScreensForPresentation — сортировка Screen слева направо, затем сверху вниз (общая сетка координат; не primary ОС как якорь).
Плейсмент окон-хостов (MfdHostWindow, PfdHostWindow) Services/PresentationHostWindowPlacement — целевой экран по индексу из пресета; иначе первый «не главный» экран; при сохранённых bounds — восстановление с прижатием к WorkingArea. На выделенном мониторе по умолчанию WindowState.Maximized; отключение — [display] maximize_presentation_host_windows_on_dedicated_screens (DisplaySettings.MaximizePresentationHostWindowsOnDedicatedScreens).
Инвариант плейсмента Позиционирование и восстановление геометрии PfdHostWindow / MfdHostWindowтолько через PresentationHostWindowPlacement (и общий жизненный цикл во MainWindow.PresentationHostWindows.axaml.cs); не плодить параллельную логику экрана / WorkingArea в других Views без расширения этого сервиса.
Персистентность геометрии хостов CascadeIdeSettings / [display]: для MFD — MfdHostWindowPixelX / PixelY / Width / Height; для PFD — PfdHostWindowPixelX / PixelY / Width / Height; запись при Closing соответствующего окна (Views/MainWindow.PresentationHostWindows.axaml.cs).
Окно-хост MFD Views/MfdHostWindow.axaml — полный MfdShellView (п. 8), тот же DataContext, что у MainWindow; слой подсветки MCP как у главного окна. Колонка под якорем Mfd в MainWindowодин MfdShellView (страницы: чат, терминал, обозреватель решения и т.д.), без отдельного зашитого сплита «дерево + shell»; второй TopLevel повторяет тот же вторичный контур (skia-surfaces-vs-overlays-v1.md), не обязательно пиксель-в-пиксель по ширине.
Жизненный цикл Нет отдельного пункта меню «второе окно» — источник истины строка presentation / zone_screen_layout; автозапуск при Loaded (см. MainWindow.PresentationHostWindows.axaml.cs): при подходящей топологии && OpenPfdHostWindowOnStartup / OpenMfdHostWindowOnStartup && достаточно мониторов — открываются PfdHostWindow и/или MfdHostWindow; опционально MCP toggle_mfd_host_window при той же топологии (CanExecute). При открытом хосте соответствующая колонка в MainWindow скрывается (SetPfdHostWindowShellOpen / SetMfdHostWindowShellOpen).
Топология внимания AttentionLayoutSurfaceKind: при хостах — MainWindowPlusMfdHostTopLevel, MainWindowPlusPfdHostTopLevel, MainWindowPlusPfdMfdHostTopLevel (MainWindowViewModel.Presentation.cs).
MCP / снимки UiLayoutSnapshot, роли mfd_host / pfd_host для соответствующих окон; ide_get_ui_layout по всем top-level.

Частично или вне текущего кода (остаётся roadmap)

  • Пресет с тремя группами (…) (…) (…) (любой порядок якорей, напр. (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.
  • Fallback при исчезновении монитора / сильной смене раскладки ОС: есть clamp к рабочим областям; отдельный UX «монитор недоступен» не описан здесь как обязательный.
  • Закрытие главного окна: при сбросе DataContext у MainWindow вызывается закрытие хоста (CloseMfdHostWindowIfOpen); явная подписка на MainWindow.Closing для каскада — не зафиксирована в этом списке (зависит от жизненного цикла приложения).
  • EICAS / IDE Health на втором экране, плотность чата/терминала — продуктовые открытые пункты («Открытые вопросы»).
  • Подтверждения агента при двух TopLevel — направление в п. 6, детали реализации — открыты.

Контекст

Сейчас одно главное окно (MainWindow) задаёт весь видимый кокпит: редактор, дерево, чат, колонка Mfd и т.д. Конфигурация режимов (0010) описывает видимость и метрики поверх фиксированного каркаса; альтернативная «схема окна» целиком в данных явно отложена.

Потребность: разнести роли по экрану без обязательной ужимки всего в одну колонку — в том числе:

  • Несколько физических мониторов — в идеале (PFD) (Forward) (MFD) (три дисплея); см. подраздел «Несколько мониторов» и таблицу схем (PFD+Forward+MFD) при дефолтном Z или (PFD|Forward|MFD) при zone_separator = "|" / (PFD+Forward) (MFD); или один широкий/изогнутый дисплей с одной парой скобок и тремя якорями в одном окне и явными зонами (см. концепт PFD/MFD, §6).
  • Вынести семантическую зону внимания на другой дисплей — в первую очередь MFD (чат, терминал, trace, тяжёлые панели агента); при необходимости PFD или компактную полосу подтверждений/статуса. Продуктовая формулировка — зона ↔ второй или третий монитор, а не абстрактное «второе окно». В коде тип MfdHostWindow, в снимке MCP поле role = mfd_host для такого второго TopLevel.
  • Эксперименты с раскладкой изолированно от стабильных пресетов — режим Flight как песочница; мультиоконность логично отрабатывать там первыми (см. §5 concept-pfd-mfd-cascade-v1.md).

0012 уже допускает отдельные окна для части хрома (телеметрия, полоски). Базовый MCP под мультиоконность уже есть: ide_get_ui_layout отдаёт JSON с массивом windows (по одному дереву на каждое открытое Window, роли main / mfd_host / other); поиск контрола по имени для инспекции/действий — с приоритетом главного окна и обходом остальных (Cockpit/Surface/UiLayoutSnapshot, UiControlAppearance). Этот ADR расширяет предмет с технического «несколько корней в снимке» на продуктовую модель нескольких окон (какие поверхности, жизненный цикл, состояние, связь с режимами и агентом); точечные дополнения контракта — при появлении новых типов окон или семантик регионов (см. п. 7).

Один монитор и три семантические зоны (0021)

Три пространственных якоря внимания (PFD, лобовое, MFD) не требуют трёх отдельных окон. Текущая реализация — одно главное окно, колонки MainGrid, топология AttentionLayoutSurfaceKind.MainWindowDockedGrid (см. playbook).

Опциональный сценарий: разнести те же семантики по нескольким TopLevel на одном физическом мониторе (например очень широкий дисплей) — один процесс, та же карта панель→зона (AttentionZonePanelRuntime), иная только геометрия презентации. По умолчанию это не цель: достаточно главного окна + вынесенной зоны на другом мониторе и/или пресета внутри одного TopLevel (0021, мотив FancyZones). Отдельно обсуждать три связанных TopLevel на одном мониторе имеет смысл только при явной обратной связи или ультрашироких сценариях; иначе риск и сложность перевешивают выигрыш над двумя окнами или сеткой в одном окне.

Не считается аргументом за мультиоконность: «заблокировать перемещение окон на уровне ОС». Прилипание, сетка и предсказуемая раскладка достигаются в рамках одного TopLevel (сплиттеры, пресеты) или программным позиционированием без обещания запрета средствами оболочки; см. 0022 (второй монитор как геометрия одного окна — ортогонально числу окон).

Несколько мониторов

Обозначения (чтобы не смешивать «зоны на одном экране» и «число дисплеев»):

  • ( … )один физический дисплей (одна область в конфигурации мониторов ОС).
  • Разделитель якорей внутри одной пары скобокровно литерал Z из zone_separator (по умолчанию +). Несколько семантических якорей (PFD, Forward, MFD) на этом же дисплее (как колонки в одном MainWindow) записываются как anchor Z anchor Z anchor. В прозе этого ADR + и | — две привычные иллюстрации одного смысла; в строке presentation при Z = "+" допустимы только +, при Z = "|" — только | (без подстановки «синонима» в парсере).
  • Краткая запись — задаётся тем же ключом pfd_zone_identifier / forward_zone_identifier / mfd_zone_identifier, например "P", "F", "M"; в строке presentation допустим только выбранный литерал (без второго «синонима» в TOML).
  • Несколько подряд (…) (…) (…)разные дисплеи; слева направо в тексте — удобная ориентировка, фактический порядок экранов задаёт геометрия и пресет. Три дисплея под три якоря — только три пары скобок, например (PFD) (Forward) (MFD). Запись без скобок вроде голого PFD | Forward | MFD двусмысленна — в конфиге и доках не использовать; символ между якорями внутри скобок — это Z, не разделитель экранов.

Ёмкость нотации и направление имён конфига (display.screens / topology)

Намеренная «ёмкость»: одна и та же строка топологии (presentation в корне settings.toml, в будущем — ключ topology внутри таблицы ниже) намеренно смешивает в одном слое (а) сколько групп дисплеев в пресете (несколько (…) (…) (…) — см. § «Несколько мониторов») и (б) как эти группы соотносятся друг с другом и с якорями (пространственный порядок в тексте, семантика зон внутри группы). Развести «число дисплеев» и «описание топологии» отдельными ключами можно было бы явнее; цена — длиннее и многословнее. Текущая нотация платит за краткость тем, что смыслы (а) и (б) не разводятся без контекста грамматики и примеров (§ грамматика, EBNF).

Направление переименования TOML (снять путаницу слова presentation с «презентацией» UI и с другими слоями — см. 0063 § CDS): канон — вложить описание топологии в уже смысловой [display] (0050, Display в модели):

  • Таблица уровня «вся топология в одном месте» (сегодня корневые ключи presentation / zone_screen_layout и связанные поля — см. код) → [display.screens] (группы экранов пресета, не «презентация» в бытовом смысле).
  • Строка EBNF / размещения якорей — ключ topology (явное имя роли).
  • Грамматика: секция [presentation_grammar][display.screens.grammar] (или согласованное вложение под тем же префиксом; имя presentation_grammar в коде — CascadeIdeSettings.PresentationGrammar).

Что такое screen в имени display.screens: в разговорном английском screen часто читается как «монитор ОС». Здесь screen — в том же духе, что screen_markers в таблице токенов: группа в строке, граница одной презентационной поверхности пресета, кокпитная метафора PFD/MFD. Семантика «одна пара ( … ) ↔ один физический дисплей в конфигурации ОС» зафиксирована выше; будущее имя таблицы не меняет парсер и семантику скобок без явной миграции кода.

Почему не display.layout: слово layout уже перегружено — CockpitPresentationLayout, 0046, плюс раскладка внутри якоря (instrument deck, 0063). Отдельная таблица [display.layout] с ключом topology была бы читаемее без «экранов», но риск смешения с внутренней раскладкой региона выше, чем у display.screens после абзаца выше. Если продукт выберет layout, в доке и комментарии к TOML зафиксировать: только многооконная / многоякорная топология пресета, не deck и не внутренняя сетка региона.

Миграция: читать старые ключи как legacy N релизов; при конфликте старого и нового — приоритет в реализации (новый канон побеждает при наличии); обновить примеры settings.toml при внедрении. Немедленная смена ключей в коде не требуется этим разделом — только зафиксированное направление; детали сроков — в задачах на загрузчик TOML.

Где хранить и зачем не только workspace.toml: раскладка по физическим дисплеямличная история (у членов команды разное железо). Репозиторный .cascade/workspace.toml (0021 §2.1, 0028) — про соглашение команды по панелям и зонам в общей модели внимания, не про обязательную для всех схему «три монитора». В settings.toml в корне задаются строки presentation и/или zone_screen_layout; опциональные токены грамматики (screen_markers, screen_separator, zone_separator, литералы якорей — см. таблицу ниже) — в секции [presentation_grammar]. Всё это задаёт топологию именно под железо пользователяпрежде всего в settings.toml (%LocalAppData%\CascadeIDE\, 0028); при merge с бандлом UiModes/ и overlay репо эти ключи берутся из пользовательского слоя и перекрывают одноимённые, если они когда-либо появятся в шипнутом или репозиторном workspace.toml (дефолт продукта, не «решение команды о мониторах»).

Грамматика строки presentation (настраивается в TOML): в том же файле, что и presentation / zone_screen_layout, в секции [presentation_grammar] задаются опциональные ключи — какие символы считать маркерами экрана, разделителем экранов, разделителем якорей и какими строками обозначать три зоны (длинные или короткие — один ключ на зону). Значения на усмотрение пользователя; ниже — дефолты, с которыми совпадают примеры по всему этому ADR:

Ключ в TOML (внутри [presentation_grammar]) Дефолт Смысл
screen_markers "()" строка из двух символов: открывающий и закрывающий границу одного дисплея
screen_separator " " разделитель между группами (дисплеями), обычно пробел между ) и следующим (
zone_separator "+" разделитель якорей внутри одной пары маркеров; в строке presentation между якорями допускается только этот литерал (не другой символ «для красоты»)
pfd_zone_identifier "PFD" литерал якоря PFD в строке presentation (регистр и написание — как задано)
forward_zone_identifier "Forward" литерал якоря лобового экрана
mfd_zone_identifier "MFD" литерал якоря MFD

Три литерала должны быть попарно различимы (сравнение без учёта регистра). При коллизии реализация сбрасывает все три идентификатора на дефолты из таблицы.

Идентификаторы якорей. Пользователь может задать, например, forward_zone_identifier = "Lob" и писать (PFD+Lob+MFD). Короткие буквы P / F / M — тем же ключом: pfd_zone_identifier = "P" и т.д.; тогда в строке только (P+F+M), без отдельных ключей «алиас».

Примеры при дефолтных идентификаторах (PFD, Forward, MFD): в строке — полные литералы, например (PFD+Forward+MFD), (PFD) (Forward) (MFD). Примеры с одной буквой на якорь — при явной настройке [presentation_grammar] с короткими pfd_zone_identifier / forward_zone_identifier / mfd_zone_identifier.

Грамматика (EBNF). Ниже — однозначное описание; литералы якорей берутся из ключей TOML (см. таблицу). Пробелы внутри пары screen_markers между якорями не допускаются (только zone_sep).

(*--- Параметры из TOML: O, C, Z, S; идентификаторы якорей PfdId, ForwardId, MfdId (дефолты — см. таблицу) ---*)

presentation ::= [ SP ] screen { SP screen } [ SP ]
screen       ::= "(" anchor { zone_sep anchor } ")"
zone_sep     ::= Z
anchor       ::= pfd_zone_identifier | forward_zone_identifier | mfd_zone_identifier

pfd_zone_identifier     ::= (* литерал из ключа pfd_zone_identifier *)
forward_zone_identifier ::= (* литерал из ключа forward_zone_identifier *)
mfd_zone_identifier     ::= (* литерал из ключа mfd_zone_identifier *)

(* после разбора: семантика PFD / лобовое / MFD — [0021](0021-pfd-mfd-cockpit-attention-model.md) *)

(* лексер: три строки якоря; совпадение в порядке убывания длины литерала; для литерала длины 1 — без учёта регистра *)

SP           ::= U+0020 { U+0020 }   (* один или более пробелов — разделитель экранов при дефолте *)

Параметризация из TOML: пусть O и C — первый и второй символ строки screen_markers, Z — строка zone_separator, S — строка screen_separator, литералы якорей — значения ключей pfd_zone_identifier, forward_zone_identifier, mfd_zone_identifier. Тогда:

presentation ::= [ S ] screen { S screen } [ S ]
screen       ::= O anchor { Z anchor } C
(* anchor — как выше; токены якорей из TOML *)

Между якорями в одном экране допускается только литерал Z из TOML (при дефолте — +). Парсер не подставляет |, + или другой символ вместо выбранного Z. Строка presentation должна использовать те же литералы, что и выбранные токены. Парсер читает сначала ключи грамматики из [presentation_grammar] (или дефолты), затем применяет соответствующую продукцию. В коде — свойство CascadeIdeSettings.PresentationGrammar; merge с бандлом — в реализации (0010, 0028).

В конфиге (например TOML): то же компактное значение можно сопроводить обычным комментарием # … — кратко пояснить для себя якоря и число дисплеев; отдельные отсылки к ADR в пользовательском файле для этого не обязательны.

Продуктовый слой (вне этого ADR): где и как давать пользователю развёрнутые объяснения (внешняя документация, справка в IDE, приоритеты) — решение уровня продукта, не ADR; см. architecture-policy.md.

Три типовых раскладки по дисплеям:

Схема Смысл
(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).
(PFD+Forward) (MFD) …; (P+F) (M) — при тех же коротких идентификаторах в TOML Два TopLevel: главное окно — PFD и лобовое вместе; второе окно — зона MFD (MfdHostWindow, вторичный контур). Типичный компромисс «два монитора».
(PFD) (Forward) (MFD) …; (P) (F) (M) — при коротких идентификаторах Три TopLevel: по одной зоне на окно (PFD; лобовое; MFD). Идеал при трёх мониторах (0021); см. расхождение с v1 в «Состояние реализации».

Канон числа окон: (P+F)(M)два верхнеуровневых окна (главное держит P+F, M вынесен); (P)(F)(M)три верхнеуровневых окна (P, F, M раздельно). Это семантика строки presentation, не «одно окно на три экрана ОС» и не три окна на одном мониторе как цель по умолчанию.

Пространственный канон (три дисплея, типичный ряд): слева направо на экранах (в порядке PresentationMonitorTopology.OrderScreensForPresentation: слева направо, затем сверху вниз) идут группы (…) (…) (…) в том же порядке, в каком они записаны в строке presentation. Первая группа — левый экран в этом порядке, вторая — средний, третья — правый. Примеры: (P) (F) (M) — слева PFD, по центру Forward, справа MFD; (M) (F) (P) — слева MFD, по центру Forward, справа PFD. Фиксированной привязки «P всегда слева» нет — задаёт только порядок скобок. Если физическая конфигурация мониторов не в один ряд, пользователь сопоставляет i-ю группу i-му экрану в выбранном порядке (или подгоняет раскладку ОС).

Доли зон на одном дисплее (опциональные веса у якорей)

Принято: внутри одной пары 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 на весь дисплей, отдельные веса не задаютсямежду группами (…) (…) весов нет: граница между мониторами — стол/геометрия ОС, не доля в строке).

Инвариант: коэффициенты меняют только доли внутри одной группы (одного экрана). Топология — сколько групп (…), какие якоря на каком экране, сколько физических дисплеев, и продуктовые следствия вроде максимизации главного окна при старте — задаются составом якорей и скобок, а не числами; подбор 0.25 vs 0.5 не переключает «два монитора на три» и не меняет семантику «на первом экране есть P и F».

Когда веса не указываются — поведение как сейчас: равные доли между якорями в группе или существующий дефолт сетки (реализация).

Внутри одной группы с двумя и более якорями: либо у каждого якоря задан коэффициент и сумма = 1, либо ни у одного (равные доли). Смешение вроде (0.25P+F+M)ошибка разбора (неоднозначно).

Одна зона в скобках — только литерал якоря, без коэффициента (или эквивалентно один якорь на весь экран).

Формат числа: десятичная точка . (как в TOML/JSON); локаль с запятой в строке presentation не поддерживается, чтобы не дублировать неоднозначность.

Связь с парсером: PresentationParser разбирает опциональные префиксы весов; внутри одной пары screen_markers структура списка якорей задаётся грамматикой Eto.Parse (PresentationInnerEtoGrammar, пакет NuGet Eto.Parse). Результат — PresentationParseResult.Screens как списки PresentationAnchorSlot (Kind + Weight?). Строки без коэффициентов по-прежнему каноничны (Weight == null на всех якорях группы).

Ниже — расширение EBNF относительно базовой продукции; при желании объединить с основным блоком EBNF выше в одну продукцию (документация).

(*--- Дополнение: веса только внутри screen с двумя и более якорями; сумма weight = 1 ---*)

weight       ::= (* положительный литерал: целое или десятичное, десятичная точка U+002E *)
weighted_anchor ::= weight anchor | anchor
screen       ::= O weighted_anchor { Z weighted_anchor } C
(* семантика: в одном screen либо каждый weighted_anchor — это «weight anchor», либо каждый — просто «anchor»; смешение запрещено; при весах sum=1 *)

(* Базовая форма без весов — как сейчас: только anchor *)

Примеры (короткие идентификаторы P, F, M в [presentation_grammar]): (0.25P+0.75F)(M); (0.2P+0.3F+0.5M); (P+F+M) — без весов, три равные доли.

Вложенные оси v и h (T-layout; пояснение для агентов и обзорной документации)

Иногда раскладку на одном физическом экране описывают формулой с явными осями, например:

0.3vPFD + 0.7v(0.8hForward + 0.2hMFD)

Расшифровка (смысл для человека и для LLM):

  1. Сначала делим экран вертикально (v): 30% ширины слева — зона PFD (контекст workspace, дерево связей, первичные индикаторы в модели кокпита).
  2. Оставшиеся 70% справа — отдельная вертикальная «полоса»; внутри неё делим горизонтально (h): верхние 80%Forward (лобовое стекло: редактор, объект работы), нижние 20%MFD (телеметрия, вторичный контур, настройки «под рукой»).

Это классический T-layout кокпита: главное (код и статус «полёта») — в центре поля зрения, вспомогательное — внизу. Без указания v / h и порядка вложенности анализатор или агент не могут однозначно сопоставить доли с RowDefinitions / ColumnDefinitions в Avalonia: сначала нужно знать, какой сплит идёт первым (здесь — вертикальный 0.3/0.7), затем — как делится правая часть по строкам (0.8/0.2).

Связь с текущей строкой presentation (реализация v1): парсер и веса в § «Доли зон» сейчас задают одну ось — типично три колонки MainGrid (PFD | Forward | MFD) с долями по ширине. Вложенная запись v / h в этом ADR — целевая семантика для будущего расширения грамматики и/или отдельного слоя композиции (см. 0036); до появления парсера с вложенными группами её нельзя считать эквивалентом одной плоской суммы (0.3P+0.7F+…) без отдельной спецификации.

Предохранитель (политика стабильности геометрии): если веса и оси заданы явно в конфиге, продукт не должен динамически пересчитывать пропорции в рантайме «по удобству» (изменение размера окна не должно подменять доли из настроек).

  • Анализатор (разбор / валидация конфигурации): в каждой группе вложенности сумма коэффициентов на этой оси = 1 (как уже для плоского экрана с весами у якорей).
  • Surface compositor (конвейер 0036): при старте сессии жёстко фиксирует вычисленные из конфига нормализованные доли (и при необходимости пиксельные границы после измерения), без последующего «плавающего» пересчёта долей при обычном ресайзе; исключения (если появятся) — отдельное явное правило продукта, не молчаливое изменение коэффициентов.

Идеал (три физических дисплея): (PFD) (Forward) (MFD) — целевая кокпитная раскладка при достаточном железе: контекст workspace / обозреватель и первичные индикаторы на первом экране, объект работы (редактор) на втором, вторичные тяжёлые панели (чат, терминал, trace…) на третьем. По канону пресет с тремя группами (…) (…) (…) означает три отдельных TopLevel (см. таблицу «Три типовых раскладки» выше), а не одно окно на три монитора без разбиения зон.

Метафора внимания (как в кокпите): помимо лобового поля зрения есть боковые зоны — их можно подхватить одним взглядом (периферийное внимание), не отрываясь от работы «вперёд» надолго. PFD и MFD в этом смысле — боковые относительно лобового (Forward), а не «второстепенные экраны»; мультиоконность и якорь направлений от экрана лобового согласуются с этой моделью.

Типичный компромисс (два дисплея): (PFD+Forward) (MFD) при дефолтном Z, либо (PFD|Forward) (MFD) при zone_separator = "|" — первый монитор: PFD и лобовое вместе (как сейчас в одном MainGrid); второй — зона MFD. Удобно и распространённо; от идеала «три экрана» отличается объединением PFD и лобового на одном дисплее.

Матрица инстансов SkiaHost (зафиксировано для v1)

SkiaHost считается на слот, а не «один на всё окно». Общее число рабочих поверхностей в типовых топологиях — три (PFD / Forward / MFD), меняется только распределение между TopLevel.

Топология (формула presentation) MainWindow MfdHostWindow Итого
(PFD+Forward+MFD) SkiaHost(PFD), SkiaHost(Forward), SkiaHost(MFD) 3 слота / 1 TopLevel
(PFD+Forward) (MFD) / (P+F) (M) SkiaHost(PFD), SkiaHost(Forward) SkiaHost(MFD) 3 слота / 2 TopLevel (канон)
(PFD) (Forward) (MFD) / (P) (F) (M) по канону — только один якорь на окно (отдельные хосты PFD и Forward — roadmap) SkiaHost(MFD) 3 слота / 3 TopLevel (канон); v1: как строка выше, т.к. P+F в MainWindow

Следствие: дополнительный «агрегирующий» SkiaHost на весь MainWindow не требуется; базовая геометрия задаётся presentation/MainGridColumnDefinitions, а поверхности остаются слот-ориентированными.

Четвёртый и далее дисплей — опционально (например полноэкранная телеметрия, второй контур Mfd, внешний браузер доков — по пресету и политике продукта).

Не единственная возможная схема; пресеты задают соответствия дисплей↔зона. Канон числа TopLevel для строки presentation — в таблице «Три типовых раскладки» и в матрице SkiaHost выше; факт кода для тройной группы — в «Состояние реализации». Снимок MCP для нескольких окон уже поддерживается (п. 7), при новых сценариях — уточнение полей/ролей в той же поставке.

Решение (принципы; детализация — по итогам обсуждения)

1. Один процесс, несколько TopLevel. Окна принадлежат одному экземпляру IDE; общее состояние решения, агента, настроек — единый граф VM/сервисов за фасадом, без второго «независимого» экземпляра приложения. Исключения (если когда-нибудь понадобятся) — отдельное решение и отдельный ADR.

2. Главный фрейм. Сохраняется понятие основного окна (редактор, решение, главная навигация по умолчанию). Дополнительные TopLevel — это в первую очередь носители вынесенных зон (см. контекст: MFD/PFD на втором или третьем мониторе); они не обязаны дублировать полный кокпит. Состав и привязка к дисплею — пресет режима и/или явное действие пользователя (например «вынести MFD» / «вынести страницу чата» (как часть вторичного контура в зоне Mfd)). Роль mfd_host в снимке MCP относится к окну-хосту зоны Mfd и не ограничивает продукт только «чатом». Закрытие главного окна — политика выхода из приложения (закрыть все дочерние или запрос подтверждения) — фиксируется в реализации, не оставлять неявным.

3. Что может жить во втором и следующих окнах (кандидаты, не обязательно всё в v1): прежде всего регион зоны Mfd на отдельном физическом дисплее — в терминах 0021 это семантика вторичного внимания; в коде тот же хост MfdShellView, внутри которого переключаются страницы SecondaryShellPage (чат, терминал, сборка, …) — страница не является «зоной», зона — Mfd как якорь внимания; при необходимости PFD или компактная полоса подтверждений/критичных индикаторов на другом мониторе; выносимые фрагменты хрома в смысле 0012. Документы редактора как floating MDI остаются вне объёма этого ADR, пока не будет отдельного решения (как в 0012).

4. Связь с режимами UI (0010). Пресеты задают видимость и слоты поверх доступных поверхностей: одно окно или несколько. Топология презентации зон (AttentionLayoutSurfaceKind: одно окно / несколько TopLevel и т.д.) согласуется с merge слоёв 0010 и отделением от карты панель→зона — см. подраздел «Топология презентации зон» в 0010. Строка раскладки по дисплеямpresentation (короткое имя) или zone_screen_layout; одно из двух, не оба. Значение — литерал из «Несколько мониторов» (например (PFD+Forward) (MFD)); рядом опционально токены грамматики из таблицы (screen_markers, screen_separator, zone_separator, литералы якорей) (EBNF). Хранение: прежде всего settings.toml пользователя (§ выше, 0028), чтобы не смешивать личную схему мониторов с командным репозиторным workspace.toml. Расширение: ключи уровня «какие панели во вынесенной зоне по умолчанию» — в TOML режима / Flight при необходимости. Валидация и enum — в реализации.

5. Сохранение геометрии. Позиции и размеры окон вынесенных зон (в т.ч. на 2-м/3-м мониторе) — не обратная запись в шипнутые UiModes/*.toml (правило 0010). В коде v1: геометрия MfdHostWindow персистится в пользовательском settings.toml (MfdHostWindowPixelX / PixelY / Width / Height — см. «Состояние реализации»); иные окна / полный snapshot workspace — при появлении отдельной договорённости.

Пресеты и выбор дисплея для вынесенной зоны (принятое направление): помимо сырого идентификатора дисплея ОС и сохранённых прямоугольников допускается семантика, естественная пользователю — направление соседства относительно экрана, на котором в текущей раскладке представлена зона лобового (forward) (0021), а не от primary монитора ОС по умолчанию. Кроссплатформенно: целевой стек — .NET + Avalonia; перечисление дисплеев и их границы в общей координатной сетке (по ним вычисляется сосед в духе left | right | up | down) — через API графической платформы на Windows, Linux и macOS; это не привязка к одной ОС. Различия между ОС (имена дисплеев, hotplug, Wayland vs X11 и т.д.) — в реализации и тестах, не в продуктовой модели якоря forward. Имена ключей и схема в данных — в той же поставке, что и мультиоконность / 0010. Tie-break: если в выбранном направлении несколько соседних дисплеев, не навязывать автоматический выбор «лучшего» — на усмотрение пользователя (явный выбор экрана в UI, уточнение пресета, либо переход на сырой идентификатор/сохранённый прямоугольник). Лобовое, разнесённое на несколько физических дисплеев — редкий случай; для v1 достаточно одного экрана якоря для forward; обобщение — при явной потребности. При смене конфигурации мониторов — fallback (сохранённая геометрия, возврат в главное окно и т.д.) остаётся предметом реализации.

Основной дисплей ОС и якорь Forward (принято): системное понятие «основной / primary» монитора (в Windows — «сделать основным дисплеем») не отождествляется с семантикой лобового экрана (Forward) в модели кокпита. Primary нужен ОС и драйверам (расположение панели задач, масштаб и т.п.); типичный конфликт — сенсорный монитор, который должен быть основным из‑за калибровки касания, хотя в рабочей раскладке пользователя лобовым по смыслу является другой дисплей. Считать Forward по умолчанию равным primary опасно — ломает такие конфигурации. Согласование физической расстановки мониторов, их порядка в настройках ОС и строки presentationответственность пользователя; продукт не подменяет расстановку стола и не выводит семантику якорей только из флага primary. Реализация сопоставляет группы (…) (…) … с дисплеями по принятой схеме (геометрия рабочих областей и порядок в строке — см. код), без тождества «первый в presentation = primary ОС».

6. Агент (ACP, 0016). Транспорт к внешнему агенту не зависит от числа окон: один канал stdio/сессия. Решение этого ADR — только куда монтируется встроенный UI ответов и подтверждений (главное окно vs дополнительное). Краткие подтверждения и критичные сигналы по-прежнему должны быть доступны в PFD-зоне внимания (концепт §3); при втором окне — либо дубль компактной полосы, либо явное правило «фокус на главном окне для подтверждения».

Подтверждения при нескольких TopLevel (принятое направление): не опираться только на системную модалку поверх «не того» окна. Показ запроса — в потоке внимания PFD (баннер/строка; презентация может быть без классической модалки на весь экран). Опционально вторичный канал внимания (звук, мигание в области уведомлений ОС), если фокус на MFD/втором окне. Ответ пользователя — те же семантики, что и у кнопок: палитра команд и жесты из 0030 — шипнутый Hotkeys/hotkeys.toml и пользовательский оверлей %LocalAppData%\CascadeIDE\hotkeys.toml (0028). До отдельного решения по протоколу: один вызов request_confirmation (и аналоги) по-прежнему завершается ok/cancel после ответа; «неблокирующее» относится к UX презентации, не к обязательной смене модели MCP.

7. MCP и паритет (0002, 0008, 0012). Уже реализовано: снимок UI для агента/тестов не ограничен одним MainWindowide_get_ui_layout строит windows[] по всем открытым Window процесса; действия по имени контрола учитывают окна помимо главного (см. контекст выше). При появлении новых продуктовых окон или новой семантики (например выделенный второй TopLevel под MFD с отдельной таксономией зон) точечно расширять контракт в той же поставке: новые значения role, поля идентификации региона/поверхности, тесты паритета — по аналогии с 0012. Мультиоконные поверхности не должны оставаться «только для человека» без осознанного исключения.

8. MfdHostWindow — только полный вторичный контур. Для раскладки вроде (PFD+Forward) (MFD) / (PFD|Forward) (MFD) второй дисплей семантически несёт всю зону Mfd: одно окно MfdHostWindow с полным MfdShellView — тем же хостом страниц (SecondaryShellPage: чат, терминал, сборка, обозреватель решения, …) и тем же DataContext (MainWindowViewModel), что и вторичный контур в главном окне. Паритет здесь — совпадение семантики вторичного контура (набор страниц внутри MfdShellView), а не обязательное воспроизведение всей визуальной колонки под якорем Mfd в MainWindow: в главном окне под якорем Mfd — один MfdShellView; инструменты вторичного внимания (включая обозреватель решения) переключаются страницами, а не отдельным зашитым сплитом «дерево + shell» (0021: зона внимания ≠ страница). Отдельный TopLevel не обязан дублировать прочие панели главного окна вне семантики вторичного контура. Не делаем отдельного продукта «второе окно только с одной страницей» (узкий хост); альтернатива «только чат» отклонена как целевое состояние. Плейсмент, первичное сохранение геометрии и привязка к индексу экрана из presentation — см. «Состояние реализации»; открыты: доводка fallback при смене мониторов, продуктовый UX (п. 5).

Последствия

  • Потребуется дизайн владения VM: общие сервисы, дочерние окна как представления или отдельные лёгкие VM с подпиской на общий сессионный слой.
  • Тесты UI и сценарии, которые жёстко предполагали только дерево MainWindow без учёта windows[] в MCP, потребуют обновления или явного двойного режима (одно окно / несколько окон); сам MCP-снимок по нескольким TopLevel уже поддерживается.
  • Документация для пользователя (позже): как вынести панель, как вернуть, где сохраняется геометрия.

Отклонённые альтернативы (как целевое состояние)

  • Полагаться только на внешний Cursor/IDE на втором мониторе без встроенной мультиоконности — отклонено как не закрывающее сценарий «одна Cascade + зоны экрана» и PFD/MFD внутри продукта (см. концепт §8 — внешние инструменты дополняют, не заменяют осмысленную раскладку Cascade).
  • Сразу вводить полный MDI документов — вне объёма; см. 0012.
  • Несколько окон на одном мониторе ради «запрета перетаскивания средствами ОС» — отклонено как мотивация: такая цель слаба и закрывается раскладкой внутри одного окна (см. подраздел «Один монитор и три семантические зоны» выше).

Уточнение: режимы UI (Power, Flight и др.)

Принято: переработка Power, Balanced и остальных пресетов/семей в смысле 0010отдельная линия работ; в объёме первой поставки второго TopLevel эти режимы не трогаем; последующая переработка пресетов и семей — отдельная линия. Мультиоконность v1 не блокируется и не смешивается с согласованием «как будет в Power». Вопрос «только Flight / Power с флагом» для мультиоконности снят до отдельной дорожной карты режимов. Flight по-прежнему удобен как полигон для экспериментов (см. контекст выше), без обязательства ограничивать продукт только им.

Открытые вопросы (для обсуждения перед кодом)

  • Три TopLevel на одном физическом мониторе (три якоря PFD/лобовое/MFD как три рамки ОС): по умолчанию не цель — достаточно связки «главное окно + вынесенная зона на другом мониторе» (2-й/3-й дисплей), мультимонитора по 0021 §13 и оси одно окно + пресет/FancyZones в 0021. Отдельный пересмотр «три на одном» — только при явной обратной связи (например ультраширокий один дисплей).
  • Второй TopLevel под зону Mfd на отдельном мониторе — см. п. 8 и «Состояние реализации»: полный MfdShellView в MfdHostWindow; семантика зон и страниц — 0021. Базовый плейсмент, сохранение bounds и автозапуск — в коде. Открыто: EICAS/IDE Health при вынесении, UX чата и терминала (плотность, клавиатура), полнота fallback при смене мониторов; переработка режимов UI — см. «Уточнение: режимы UI».
  • Подтверждения агента (детали продукта): таймауты, деструктивные операции, нужен ли отдельный модальный слой процесса — при реализации поверх принятого направления (п. 6 выше).
  • Пресеты и мониторы (детали реализации): принятый ориентир — см. п. 5 выше (якорь forward, соседство left | right | up | down). Tie-break при нескольких соседях в одном направлении — на стороне пользователя (см. п. 5), не обязательная эвристика в продукте. Ключи раскладки по дисплеямpresentation / zone_screen_layout и токены грамматики — персональные, см. п. 4 и § слой хранения; открытая деталь реализации: полнота fallback при несовпадении раскладки ОС.

Принятие

2026-04-11 — статус Accepted; ссылка из architecture-policy.md. concept-to-implementation-map-v1.md §6 — мультиоконность и MfdHostWindow (актуальная привязка к файлам). 2026-04-11 — раздел «Состояние реализации»: синхронизация ADR с кодом (топология, плейсмент, персистентность bounds).


История изменений

Дата Изменение
2026-04-08 0021, 0010; отклонённая мотивация «запрет перетаскивания ОС».
2026-04-11 доли зон внутри одного дисплея (опциональные коэффициенты у якорей; между группами (…) (…) весов нет).
2026-04-11 п. 5 доп. primary vs Forward.
2026-04-11 п. 8: MfdHostWindow — только полный MfdShellView (все страницы); узкий одностраничный хост не делаем.
2026-04-11 (PFD+Forward) (MFD) и аналоги: второй экран = полный MFD.
2026-04-11 presentation / zone_screen_layout: прежде всего settings.toml (0028), не командный репо — см. § слой хранения, п. 4, 0010.
2026-04-11 zone_separator строгий: парсер не подставляет \| вместо + и наоборот — см. подраздел «Несколько мониторов», EBNF.
2026-04-11 без отдельных *_zone_alias: один литерал на зону — только pfd_zone_identifier / forward_zone_identifier / mfd_zone_identifier; короткие имена — тем же ключом (например pfd_zone_identifier = "P").
2026-04-11 грамматика presentation: EBNF в §; ключи TOML — §.
2026-04-11 идеальная мультимониторная раскладка: (PFD) (Forward) (MFD) (три дисплея); метафора боковых зон кокпита (лобовое + боковые поля одним взглядом); компромисс «два монитора»; окно-хост зоны Mfd (MfdHostWindow), роль в MCP mfd_host; три TopLevel на одном мониторе — не цель по умолчанию; v1 / MfdShellView уточнён по коду (MainWindow.axaml, SecondaryShellPage); MCP windows[] (п. 7); подтверждения агента (0030); пресеты и второй дисплей: якорь направлений — экран forward (лобовое), не primary ОС; соседство left \| right \| up \| down; дисплеи — кроссплатформенно (.NET + Avalonia: Windows, Linux, macOS); лобовое на нескольких дисплеях — вне v1; tie-break (несколько соседей в одном направлении) — на усмотрение пользователя, не автоэвристика;
2026-04-11 литералы якорей в TOML (pfd_zone_identifier, …) — § таблица, EBNF.
2026-04-11 обозначения дисплеев: разделитель якорей внутри скобок — литерал zone_separator (Z, по умолчанию +); в прозе + и \| иллюстрируют один смысл, в строке presentation — только выбранный Z; несколько пар (…) (…) (…) — несколько дисплеев — см. «Несколько мониторов»; в TOML — пояснение в # без обязательных отсылок к ADR; продуктовая документация — architecture-policy.md.
2026-04-11 режимы UI (Power и др.): не в scope первой поставки второго TopLevel; переработка режимов — отдельно; вопрос Flight vs Power для мультиоконности снят до той линии.
2026-04-11 статус: разделение «уже реализовано» / «дорожная карта» (парсер и TOML — не backlog).
2026-04-11 терминология: зона внимания Mfd vs страницы SecondaryShellPage во MfdShellView (чат — страница, не зона).
2026-04-11 токены грамматики в settings.toml — секция [presentation_grammar] (модель CascadeIdeSettings.PresentationGrammar).
2026-04-11 статус Accepted; навигатор architecture-policy.md.
2026-04-16 skia-surfaces-vs-overlays-v1.md (Skia-поверхности vs оверлеи, отказ от зашитых сплитов в пользу зон презентации); п. 8 уточнён: обозреватель решения — страница вторичного контура, не отдельный сплит в колонке Mfd MainWindow.
2026-04-17 Канон числа TopLevel: (P+F)(M) ⇒ два окна; (P)(F)(M) ⇒ три; слева направо = порядок групп в строке; плейсмент хостов — только PresentationHostWindowPlacement.
2026-04-18 § display.screens / topology; согласование с 0063.