Forge
markdowndeeb25a2
1# ADR 0123: Intercom — эволюция к full-Skia surface (плотный Slack/MM-like UX)
2
3**Статус:** Accepted
4**Дата:** 2026-05-18
5
6## Связанные ADR
7
8| ADR | Роль |
9|-----|------|
10| [0044](0044-avalonia-host-skia-agent-chat-surface.md) | Хост Avalonia + Skia для ленты; модель первична; без «весь чат Skia одним махом» |
11| [0057](0057-chat-surface-pipeline-adoption.md) | Pipeline snapshot → entities → layout → render |
12| [0117](0117-ide-skia-kit.md) | `Views/SkiaKit/` — примитивы; чат собирает сцену из kit |
13| [0119](0119-chat-slash-commands-intercom-surface.md) | Слэши и autocomplete в `ChatInput` |
14| [0120](0120-primary-work-surface-intercom-or-editor.md) | Intercom в Forward — якорь внимания; компактный chrome |
15| [0121](0121-intent-oriented-programming-paradigm.md) | Intercom — центр вокруг цели; структура важнее «ленты ради ленты» |
16| [0126](0126-intercom-inspect-slash-and-compact-chrome-status.md) | Compact: статус в toolbar; навигационный хвост убран из скролла |
17| [0031](0031-agent-chat-clarification-batches-and-threading.md) | Пакеты уточнений — структурированный UI |
18| [0066](0066-cockpit-ui-vs-ide-presentation-layer.md) | Cockpit UI vs IDE chrome; Intercom — **не** «ещё один Avalonia-экран» |
19| [0076](0076-ui-ux-principles-hub.md) | Плотность, токены; **UiKit** — shell/MFD-настройки, не лента Intercom |
20| [0072](0072-chat-topic-cards-intent-melody-keyboard-contract.md) | Topic cards, overview/detail |
21| [0098](0098-semantic-first-document-as-projection.md) | Редактор кода — тяжёлый контур, остаётся на AvaloniaEdit |
22
23### Вне ADR
24
25| Документ | Роль |
26|----------|------|
27| [feature-archetype-v1.md](../design/feature-archetype-v1.md) | Skia-surface: Intent → Layout → Render; примитивы из SkiaKit |
28| [ide-chrome-tokens-v1.md](../design/ide-chrome-tokens-v1.md) | Токены Avalonia chrome; мост в `SkiaChatTheme` |
29| [iop-manifest-v1.md](../iop-manifest-v1.md) | Intercom как hub коммуникации |
30
31---
32
33## Резюме
34
35- **Принцип (согласовано с [0044](0044-avalonia-host-skia-agent-chat-surface.md)):** **Avalonia — только фюзеляж** (окно, зоны PFD/Forward/MFD, lifecycle, DPI). **Intercom** — **наш** Skia-surface (лента, composer, slash, toolbar). Стандартные Avalonia-контролы в чате (`TextBox`, `ListBox`, `CascadeSection` в ленте) — **не целевое состояние**; визуально они выбиваются из продукта («из-под топора») и противоречат разделению слоёв [0066](0066-cockpit-ui-vs-ide-presentation-layer.md).
36- **На Avalonia остаются «тяжёлые» контуры:** прежде всего **редактор кода** (AvaloniaEdit), плюс узкий класс **IDS-оверлеев** с богатыми формами (пакеты уточнений [0031](0031-agent-chat-clarification-batches-and-threading.md), модалки настроек) — не как постоянная «оболочка чата».
37- **Целевая картина:** один control **`IntercomSkiaSurface`** (эволюция `SkiaChatSurfaceControl`) — лента + composer + slash popup; Slack/MM-like плотность.
38- **Strangler, не big-bang:** текущий `ChatPanelView` с Avalonia — **временный мост**, подлежит сужению до пустого `Panel`-хоста.
39- **Код v0 (2026-05-18):** compact layout на Avalonia — **тактический** компромисс до фазы 1–2; **не** эталон UX.
40
41---
42
43## Контекст
44
45После [0120](0120-primary-work-surface-intercom-or-editor.md) Intercom может занимать **Forward**. Пользователь ожидает опыт уровня **Cursor / Slack / Mattermost**: одна вертикальная «колонна разговора», минимум рамок, максимум смысла на пиксель.
46
47**Сейчас в коде:**
48
49| Слой | Технология | Содержимое |
50|------|------------|------------|
51| Лента, topic cards, spine strip | **Skia** (`SkiaChatSurfaceControl`, entity pipeline) | Сообщения, overview, ветки |
52| Шапка, spine-редактор, уточнения | **Avalonia** (`ChatPanelView`, UiKit) — **долг** | Секции, TextBox, ListBox |
53| Ввод + slash autocomplete | **Avalonia** — **долг** | [0119](0119-chat-slash-commands-intercom-surface.md) |
54
55Два визуальных «мира» в одной панели: Skia выглядит как продукт, Avalonia-остров — как чужой Fluent-виджет. При `primary_work_surface = intercom` это блокирует позиционирование Intercom как центра сессии ([0121](0121-intent-oriented-programming-paradigm.md)).
56
57---
58
59## Проблема
60
611. **Чужой chrome:** стили `cascadeSection` / Fluent `TextBox` не дают плотного «мессенджерного» ритма; полировка UiKit **не** решает — нужен **свой** render-слой.
622. **Нарушение [0044](0044-avalonia-host-skia-agent-chat-surface.md):** в зоне агента снова вырастает полноценный Avalonia-UI вместо «фюзеляж + прибор».
633. **Два скролла / два ритма:** лента Skia vs composer Avalonia — нет **единой колонны**.
644. **Ограничение роста:** группировка, meta-строки, inline code, slash-popup — в Skia; наращивание Avalonia-контролов — тупик.
655. **Риск big-bang:** IME/ввод/доступность — инженерная задача фазы 2, а не повод оставлять `TextBox` навсегда ([0044](0044-avalonia-host-skia-agent-chat-surface.md) отверг только «одним махом без модели», не Skia-composer как цель).
66
67---
68
69## Целевое UX (ориентиры Slack / MM)
70
71Не копировать пиксель-в-пиксель, а зафиксировать **инварианты плотности**:
72
73| Инвариант | Смысл |
74|-----------|--------|
75| **Одна колонна** | Вертикальный поток: (опционально) toolbar → лента → composer; один scroll для истории |
76| **Роли визуально различимы** | user / assistant / system / tool — фон, выравнивание или полоска, без «простыни одного стиля» |
77| **Meta одной строкой** | Автор · время · ветка — 11–12px, приглушённый цвет; тело 13–14px |
78| **Группировка** | Подряд идущие сообщения одного автора — без повторения заголовка (фаза 2+) |
79| **Composer всегда снизу** | Фиксированная зона ввода; slash-list **над** полем, не отдельная «карточка» |
80| **Overview не ломает колонну** | Картотека тем — режим той же поверхности (уже есть `OverviewMode`), не отдельная «страница Avalonia» |
81
82**Spine:** в Forward — **только** компактная Skia-полоска ([0096](0096-intercom-topic-card-summary-and-product-spine.md)); полный редактор spine — MFD / палитра / modal (IDS), не Expander в ленте.
83
84---
85
86## Решение
87
88<a id="adr0123-p0"></a>
89
90### Принцип: фюзеляж vs наш surface
91
92| Слой | Технология | Примеры |
93|------|------------|---------|
94| **Фюзеляж** | Avalonia | `MainWindow`, `AttentionZoneContainer`, splitters, `MfdHostWindow`, **пустой** host `Panel` для Intercom |
95| **Тяжёлый контент** | Avalonia + специализированные контролы | **AvaloniaEdit**, Terminal, Git diff views, Rich settings |
96| **Intercom (цель)** | **Skia** (+ SkiaKit) | Лента, topic cards, composer, slash popup, toolbar chip |
97| **IDS / редкие формы** | Avalonia overlay **поверх** Skia | Clarification batch, мастера; не встроенные `Expander` в ленте |
98
99**Запрещено как steady state:** `ChatPanelView` с `CascadeSection` + `TextBox` + `ListBox` как основной UX Intercom.
100
101<a id="adr0123-p1"></a>
102
103### Поэтапный strangler
104
105### Фаза 0 — тактический долг (2026-05-18)
106
107- `primary_work_surface = intercom`, `SkiaChatDensity` / `CompactLayout`.
108- Урезанный Avalonia chrome — **временно**, до выноса toolbar/composer в Skia.
109
110### Фаза 1 — **Единая Skia-колонна (лента + chrome)**
111
112`IntercomSkiaSurface` (rename от `SkiaChatSurfaceControl`) на **весь** клиент зоны; `ChatPanelView` → `Content="{Binding …}"` одного control или `Panel` без дочерних Avalonia-виджетов:
113
114- Toolbar, chip загрузки, кнопка overview — **рисуются в Skia** (или одна строка в layout engine).
115- Никаких `CascadeSection` / `Expander` spine в Forward.
116- Один вертикальный scroll истории внутри surface.
117
118**Критерий:** в дереве визуала под Forward для Intercom — **нет** `TextBox`/`ListBox`/`Button` Fluent-стиля (кроме будущего IDS-overlay).
119
120### Фаза 2 — **Skia composer + slash (целевой ввод)**
121
122- Composer: Skia-рамка, placeholder, кнопка send, многострочный рост по содержимому.
123- **Ввод текста:** **`ITextInputMethodClient`** на Avalonia **12** (`TopLevel.TextInputMethod.SetClient`) + отрисовка composer в Skia из `ChatInput` (preedit через `SetPreeditText`). **Без** видимого Fluent `TextBox` в steady state.
124- Slash: `SkiaPopupList` (SkiaKit), hit-test; данные из `ChatSlashAutocomplete` ([0119](0119-chat-slash-commands-intercom-surface.md)).
125- Клавиши: `ChatSendKeyMatcher`, Tab/↑/↓/Esc — на `IntercomSkiaSurface`.
126
127**Критерий:** пользователь не видит «чужих» Avalonia-контролов в Intercom; ввод не хуже нативного по кириллице/IME на Windows (ручной чеклист).
128
129**Отклонено для steady state:** «прозрачный `TextBox` поверх Skia» — оставляет Fluent-семантику и дублирует фюзеляжный UI в продуктовой зоне.
130
131### Фаза 3 — **Богатая лента (ёмкость)**
132
133- Группировка сообщений, схлопнутые блоки кода (mono strip), сворачиваемый «thinking».
134- ~~Markdown subset в Skia~~ → **v1.1:** `SkiaMarkdownLayout` в prose; fenced — `ChatMessageBodyPresentation` — канон [0129](0129-intercom-message-body-markdown-and-fenced-code.md).
135- ~~Копирование выделения~~ → **v1.1:** выбранное сообщение + Ctrl+C → clipboard (`ChatSurfaceSnapshotMessageLookup`).
136
137### Фаза 4 — **Только тяжёлые IDS-оверлеи на Avalonia**
138
139- **Clarification batch** ([0031](0031-agent-chat-clarification-batches-and-threading.md)) — отдельный **overlay** / flyout (IDS), не секция в `ChatPanelView`; допустимы Avalonia-формы как «тяжёлый» контур.
140- **AI settings**, **Terminal**, **Editor** — без изменений по технологии.
141- **MFD → Chat page:** после стабилизации Forward — **тот же** `IntercomSkiaSurface` (comfortable density), **не** отдельный UiKit-экран.
142
143<a id="adr0123-p2"></a>
144
145### Архитектурные границы (не менять)
146
147```
148ChatSurfaceSnapshot (Features/Chat, CCU)
149
150ChatSurfaceEntityFactory → ISkiaChatEntity[]
151
152SkiaChatLayoutEngine → SkiaChatPlacedEntity[]
153
154Skia draw (Views/Chat/Skia, SkiaKit)
155```
156
157- ViewModel **не** импортируется из Skia-слоя ([0117](0117-ide-skia-kit.md)).
158- Новые примитивы composer/slash — сначала **SkiaKit** (например `SkiaComposerStrip`, `SkiaPopupList`), затем использование в чате.
159- Токены: `SkiaChatTheme` / `SkiaKitThemeBridge` ← `CascadeTheme.*` ([ide-chrome-tokens-v1.md](../design/ide-chrome-tokens-v1.md)).
160
161### Плотность (один surface, два пресета)
162
163| Пресет | Когда | Метрики |
164|--------|-------|---------|
165| **Compact** | Forward + `primary_work_surface = intercom` | `SkiaChatDensity` |
166| **Comfortable** | MFD Chat, узкая колонка | чуть больше padding, те же примитивы |
167
168Один `IntercomSkiaSurface`, без второго Avalonia-макета.
169
170---
171
172## Отклонённые альтернативы
173
174| Альтернатива | Почему нет |
175|--------------|------------|
176| **Дальше полировать UiKit в чате** | Не достигает Slack/MM плотности; закрепляет «два мира»; против [0066](0066-cockpit-ui-vs-ide-presentation-layer.md) |
177| **Прозрачный Avalonia `TextBox` в composer** | Всё ещё Fluent-контрол в продуктовой зоне; против принципа фюзеляжа |
178| **Всё на Avalonia** `ItemsControl` | Против [0044](0044-avalonia-host-skia-agent-chat-surface.md) |
179| **Full-Skia для clarification forms** | Слишком дорого; оверлей IDS на Avalonia — ок |
180| **Big-bang без фаз 1–2** | Риск IME; [0044](0044-avalonia-host-skia-agent-chat-surface.md) |
181| **Отдельное окно чата** | Ломает [0017](0017-multi-window-workspace-and-agent-surfaces.md), [0120](0120-primary-work-surface-intercom-or-editor.md) |
182
183---
184
185## Последствия
186
187- **Плюсы:** единый визуальный язык, плотность, проще анимации и hit-test; лучше стык с semantic/graph surfaces ([0067](0067-graph-backed-surfaces-contract.md)).
188- **Минусы:** две реализации composer (compact/comfortable) до схождения; тесты UI сложнее — опора на snapshot/layout unit tests + немного headless Skia bounds tests.
189- **MCP / агент:** без изменений wire; `ChatInput` для MCP может оставаться VM-строкой, UI — проекция.
190
191---
192
193## Метрики успеха (для принятия Implemented)
194
1951. Forward + intercom: **один** визуальный scroll-контур для истории.
1962. В Intercom **нет** видимых Avalonia `TextBox` / `ListBox` / `CascadeSection` (кроме IDS-overlay уточнений).
1973. Плотность: ≥ на 20% больше видимых строк истории при 1080p на той же истории (эвристика, сравнение до/после скриншота).
1984. Регрессии: [0119](0119-chat-slash-commands-intercom-surface.md) autocomplete, Enter-send, overview/detail — зелёные тесты/ручной чеклист.
199
200---
201
202## Открытые вопросы
203
2041. **Platform input:** `InputMethod` + offscreen presenter vs отдельный нативный HWND для composer на Windows — спайк в фазе 2.
2052. *(закрыто — [0129](0129-intercom-message-body-markdown-and-fenced-code.md))* лента: Skia subset + fenced; полный MD — preview [0069](0069-markdown-preview-tool-surface-and-renderer-decoupling.md) по действию на сообщении.
2063. **Clarifications IDS:** bottom sheet vs боковая панель (оба — Avalonia overlay, не встроенный блок).
2074. **Приоритет:** фаза 1 (убрать Avalonia-остров) перед фазой 3 (группировка сообщений) — **рекомендуется**.
208
209---
210
211## История изменений
212
213| Дата | Изменение |
214|------|-----------|
215| 2026-05-18 | Proposed: full-Skia evolution, фазы 0–4, открытые вопросы. |
216| 2026-05-18 | Уточнение: Avalonia только фюзеляж + тяжёлые контуры; отказ от steady-state Avalonia в Intercom; composer через `ITextInputMethodClient` (Avalonia 12), не Fluent TextBox. |
217| 2026-05-18 | **Accepted.** Фаза 1 в коде: Forward shell, Skia toolbar, `intercomComposer` strip (временный TextBox до фазы 2). |
218| 2026-05-17 | **Implemented (фазы 1–3 v1):** `IntercomSkiaSurface`, Skia composer + `SkiaPopupList` + IME client; MFD на том же surface; группировка ленты, `SkiaMonoCodeStrip`, double-click thinking toggle. Markdown subset и copy — открыто. |
219| 2026-05-18 | **Render fix:** offscreen `WriteableBitmap` + `DrawImage` (не `SKCanvas.Clear` на leased canvas окна — [Avalonia #5932](https://github.com/AvaloniaUI/Avalonia/issues/5932)). |
220| 2026-05-18 | **Фаза 3 (v1.1):** inline Markdown subset (`**` / `*` / `` ` ``) в prose; Ctrl+C копирует тело выбранного сообщения; MFD Chat на `IsSkiaIntercomHostVisible` (comfortable `CompactLayout=false`). |
221| 2026-05-20 | **Flat feed в ленте:** `SkiaChatBubbleKind.Feed` — user/agent/thinking/tool, slash outcome, clarification, nav-строки без messenger-пузыря; акцент — selection / branch / ошибка. |
View only · write via MCP/CIDE