| 1 | # Playbook: существительные → сущности, глаголы → методы v1 |
| 2 | |
| 3 | **Назначение:** общий принцип декомпозиции кода и UI-рендеринга для агента. Не привязан к одному модулю (чат, Skia, MCP). **Связь:** `worlds/software-authoring/code-writing-principles-v1.md` (инженерные принципы), домен **software.authoring**. |
| 4 | |
| 5 | **Версия:** v1.0 · 2026-05-17 — зафиксировано после рефакторинга Cascade IDE chat surface (`SkiaChatMessage`, `SkiaChatConfirmation`, `SkiaChatTopicCard` вместо `ItemKind` + god-`switch`). |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Контракт (одна строка) |
| 10 | |
| 11 | **Существительные в предметной области = именованные типы (сущности). Глаголы = методы этих сущностей (или сервисов), а не ветки в одном процедурном методе.** |
| 12 | |
| 13 | --- |
| 14 | |
| 15 | ## Когда применять (триггеры) |
| 16 | |
| 17 | - В задаче или в коде фигурируют **отдельные понятия** (сообщение, тема, уточнение, карточка, заказ, сессия) — до правки пикселей/логики спросить: **есть ли тип?** |
| 18 | - Появляется **второй** `if` по `kind` / `type` / `enum` для **разной формы** одного слоя (отрисовка, layout, hit-test) → **новая сущность**, не ещё одна ветка. |
| 19 | - Файл control/presenter/renderer **растёт** за счёт `switch` по дискриминатору — вынести **сущности + Measure/Draw** (или аналог), control оставить оркестратором. |
| 20 | - Задача сформулирована как «сделать визуал X» — **сначала** модель сущностей из домена (уже есть в snapshot/DTO/compositor), **потом** отрисовка. |
| 21 | |
| 22 | --- |
| 23 | |
| 24 | ## Правила |
| 25 | |
| 26 | ### 1. Существительные → сущности |
| 27 | |
| 28 | | В речи / домене | В коде (плохо) | В коде (хорошо) | |
| 29 | |-----------------|----------------|-----------------| |
| 30 | | Сообщение | `ItemKind.Message` + поля в общем record | `Message` / `SkiaChatMessage` с `Title`, `Text`, … | |
| 31 | | Уточнение | `Kind == Confirmation` | `Confirmation` | |
| 32 | | Карточка темы | `TopicCard` как enum-ветка | `TopicCard` как класс: `Measure`, `Draw` | |
| 33 | | Режим «в фокусе» | ещё один `Accent` | поле сущности `IsFocused` или подтип | |
| 34 | |
| 35 | **Прилагательные и статусы** (`pending`, `active`, `focused`) — поля или малые value-объекты у сущности, не размытый `string Accent` на всё подряд. |
| 36 | |
| 37 | ### 2. Глаголы → методы |
| 38 | |
| 39 | | Действие | Плохо | Хорошо | |
| 40 | |----------|-------|--------| |
| 41 | | Измерить высоту | 80 строк в `BuildLayout` с `switch` | `entity.Measure(context)` | |
| 42 | | Нарисовать | `DrawOperation` с ветками по kind | `entity.Draw(context, top, layout)` | |
| 43 | | Открыть тему | флаг в общем hit | `CreateHit()` у сущности / явный `SelectThreadId` в hit-модели | |
| 44 | |
| 45 | Оркестратор (control, scene builder) **не знает деталей** отрисовки каждого вида — только цикл по `ISkiaChatEntity` / списку полиморфных сущностей. |
| 46 | |
| 47 | ### 3. Слои |
| 48 | |
| 49 | 1. **Домен / snapshot** (уже есть: `ChatSurfaceEntry`, `ChatThreadOverviewItem`) — источник истины по данным. |
| 50 | 2. **Сцена / presentation** — `SceneBuilder` собирает список сущностей UI-слоя. |
| 51 | 3. **Сущность** — layout + draw + hit. |
| 52 | 4. **Control** — scroll, theme, input, цикл `foreach (placed) entity.Draw(...)`. |
| 53 | |
| 54 | Не смешивать шаги 3 и 4 в одном классе на сотни строк. |
| 55 | |
| 56 | ### 4. Анти-паттерн «быстрое закрытие задачи» |
| 57 | |
| 58 | RLHF и короткие сессии толкают к **патчу в существующий switch** — задача закрыта, структура отложена. Этот playbook — **явный стоп-кран**: если сущность названа в домене, она должна появиться в коде слоя отображения **до** тюнинга цветов и отступов. |
| 59 | |
| 60 | --- |
| 61 | |
| 62 | ## Быстрый чеклист перед коммитом |
| 63 | |
| 64 | - [ ] Каждое **существительное** из UI/домена имеет **тип** (класс/record), а не только значение enum. |
| 65 | - [ ] Каждый **глагол** (measure, draw, hit, open, send) — **метод**, а не очередная ветка `if (kind == …)`. |
| 66 | - [ ] Второй похожий `kind` в одном renderer → вынос в сущность или shared helper **с именем домена**, не «ещё case». |
| 67 | - [ ] Control / ViewModel **< ~300–400 строк** на оркестрацию; детали — в сущностях и builder. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## Пример (Cascade IDE, chat Skia) |
| 72 | |
| 73 | **Было:** `ItemKind`, `ItemSnapshot`, `SkiaChatLayout.BuildLayout` + `DrawOperation` с `switch` по kind. |
| 74 | |
| 75 | **Стало:** |
| 76 | |
| 77 | - `SkiaChatSceneBuilder` → `Message`, `Confirmation`, `TopicCard`, … |
| 78 | - `ISkiaChatEntity`: `Measure`, `Draw`, `CreateHit` |
| 79 | - `SkiaChatSurfaceControl` — только theme, scroll, hit-test, цикл отрисовки |
| 80 | |
| 81 | См. `cascade-ide/Views/Chat/Skia/`. |
| 82 | |
| 83 | --- |
| 84 | |
| 85 | ## Связанные документы |
| 86 | |
| 87 | - `worlds/software-authoring/code-writing-principles-v1.md` — SOLID, узкие типы, композиция |
| 88 | - `worlds/hci-ux-dx/playbook-hci-core-v1.md` — когда трогать UI/UX |
| 89 | - `worlds/software-dotnet-avalonia/playbook-avalonia-dock-ui-v1.md` — Avalonia/Skia в Cascade IDE |
| 90 | |
| 91 | <!-- section:ooad-foundation --> |
| 92 | ## Фундамент: OOA&D |
| 93 | |
| 94 | Этот playbook — **сжатая операционная выжимка** классического **объектно-ориентированного анализа и проектирования** (noun/verb analysis, CRC, GRASP). |
| 95 | |
| 96 | | Слой | Файл | |
| 97 | |------|------| |
| 98 | | Теория и источники (Larman, UML, связь с DDD/GoF) | `kb-ooad-fundamentals-v1.md` | |
| 99 | | Пошаговый проход для агента (7 шагов) | `playbook-ooad-agent-operational-v1.md` | |
| 100 | | Статус домена | `status-software-authoring-v1.md` | |
| 101 | |
| 102 | **Правило:** при новой подсистеме или рефакторинге renderer — сначала **операционный OOA&D-проход**, затем этот чеклист при коммите. |
| 103 | <!-- /section:ooad-foundation --> |
| 104 | |
| 105 | |