mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 21:58:56 +00:00
761 lines
77 KiB
Markdown
761 lines
77 KiB
Markdown
# HP-UX-11 — стабильная панель редактора и контекстная суб-панель
|
||
|
||
Статус: **реализовано локально 2026-08-09; целевой прогон тестов отложен до следующего пре-релиза по принятому процессу проекта**
|
||
Дата: **2026-08-08**
|
||
Область: **редактор плана, редактор устройств, редактор подложки и все будущие редакторы House Plan**
|
||
Тип изменения: **UX и архитектура клиентского интерфейса без изменения модели данных**
|
||
|
||
## 1. Продуктовое решение
|
||
|
||
Основная панель каждого редактора содержит только постоянные инструменты и постоянные действия этого редактора. Выделение объекта, начало незавершённой операции или появление другого временного контекста **не изменяет высоту основной панели**.
|
||
|
||
Все действия и элементы управления, которые появляются только из-за текущего выделения или временного состояния, выводятся в единой **контекстной суб-панели**:
|
||
|
||
- суб-панель появляется непосредственно под основной панелью;
|
||
- располагается поверх рабочей области, а не в её layout-flow;
|
||
- не уменьшает рабочую область;
|
||
- не меняет масштаб, pan, `viewBox` и координатное преобразование плана;
|
||
- имеет полупрозрачную поверхность с достаточной контрастностью;
|
||
- исчезает вместе с контекстом;
|
||
- используется одинаково во всех редакторах.
|
||
|
||
Та же поверхность закладывается как **общий второй уровень editor chrome**. В будущем одна постоянная кнопка основной панели сможет открывать в суб-панели семантически связанную группу инструментов или команд. Например, вместо нескольких соседних кнопок в основной панели остаётся один launcher группы, а её варианты раскрываются под панелью поверх сцены.
|
||
|
||
Таким образом, у суб-панели есть два равноправных режима вызова:
|
||
|
||
1. **автоматический контекст** — появляется из-за selection/operation/tool state;
|
||
2. **явная группа** — открывается пользователем по кнопке основной панели как компактное подменю второго уровня.
|
||
|
||
Это один компонент и один overlay-host, но не одна и та же модель взаимодействия: автоматический контекст не забирает focus, а явно открытая группа управляется как раскрываемая панель и возвращает focus своему launcher.
|
||
|
||
Кнопка **«Закрыть редактор»** остаётся постоянной частью основной панели, закрепляется у её логического правого края и не переносится вследствие появления контекстных действий.
|
||
|
||
### Главный инвариант
|
||
|
||
> В пределах одного открытого редактора и неизменной ширины карточки изменение выделения, активного объекта или промежуточного состояния операции не должно менять геометрию рабочей области.
|
||
|
||
Иными словами, до и после выделения объекта должны оставаться неизменными:
|
||
|
||
- верхняя координата рабочей области;
|
||
- её ширина и высота;
|
||
- масштаб плана;
|
||
- положение плана;
|
||
- экранная позиция кнопки выхода из редактора;
|
||
- соответствие экранных координат координатам плана.
|
||
|
||
## 2. UX-оценка предложения
|
||
|
||
Оценка: **сильное и приоритетное UX-улучшение**.
|
||
|
||
Проблема не косметическая. Текущая панель участвует в расчёте доступной высоты сцены. Когда «Свойства» и «Удалить» создают новую строку, рабочая область уменьшается, а пользователь видит сразу несколько нежелательных эффектов:
|
||
|
||
1. выбранный объект визуально смещается под курсором;
|
||
2. меняется доступный масштаб и композиция плана;
|
||
3. интерфейс выглядит нестабильным;
|
||
4. кнопка выхода перестаёт быть постоянным пространственным ориентиром;
|
||
5. на пограничных ширинах одна небольшая кнопка может вызвать перенос всей группы;
|
||
6. при последовательном выборе объектов с разным набором действий панель «дышит» и отвлекает от геометрической работы.
|
||
|
||
Предложенная суб-панель устраняет причину, а не маскирует её анимацией высоты. Цена решения умеренная: потребуется общий host-компонент и перенос существующих динамических действий, но модель плана и сами команды не меняются.
|
||
|
||
Расширение до явно открываемых групп дополнительно повышает ценность решения: вместо одноразового исправления двух кнопок появляется устойчивый второй уровень editor chrome. Это позволит разгружать основную панель без новых dropdown-компонентов в каждом редакторе. Главный UX-риск — чрезмерно спрятать инструменты, поэтому группы создаются только вручную для тесно связанных вариантов, имеют постоянное имя/chevron и не возникают автоматически из-за ширины экрана.
|
||
|
||
### Что не следует выбирать вместо этого
|
||
|
||
| Альтернатива | Почему не рекомендуется |
|
||
|---|---|
|
||
| Плавно анимировать высоту существующей панели | Дёрганье станет мягче, но рабочая область, масштаб и положение объектов всё равно будут изменяться |
|
||
| Всегда резервировать пустую вторую строку | Устранит сдвиг, но постоянно отнимет полезную высоту у плана |
|
||
| Спрятать действия в меню `…` | Сэкономит место, но ухудшит обнаруживаемость частых «Свойств» и «Удалить» |
|
||
| Показывать кнопки рядом с выбранным объектом | Панель будет перекрывать геометрию, прыгать при pan/zoom и конфликтовать с маленькими объектами |
|
||
| Закрепить действия снизу карточки | Слабее связывает действия с редактором и конфликтует с узкими/мобильными раскладками |
|
||
|
||
## 3. Причина дефекта в текущей архитектуре
|
||
|
||
Сейчас `.editbar` является обычным flex-контейнером с `flex-wrap: wrap`. Контекстные кнопки добавляются в тот же DOM-flow, что и инструменты, параметры и кнопка закрытия.
|
||
|
||
`editorchrome` участвует в вертикальной компоновке карточки. Его фактическая высота отслеживается, а высота сцены рассчитывается с учётом этой величины. Поэтому перенос кнопок на следующую строку является не только изменением панели, но и изменением геометрии сцены.
|
||
|
||
Существующая анимация высоты оправдана при явном переходе **между редакторами**, когда меняется вся рабочая среда. Она не должна запускаться при выборе объекта внутри одного редактора.
|
||
|
||
## 4. Термины
|
||
|
||
- **Основная панель** (`primary editor toolbar`) — постоянная панель инструментов активного редактора.
|
||
- **Постоянное действие** — действие, доступное независимо от выделения и промежуточной операции: выбор инструмента, Undo/Redo, выход из редактора.
|
||
- **Контекстное действие** — действие, смысл которого существует только при конкретном выделении или временном состоянии: «Свойства», «Удалить», «Сбросить незавершённый контур».
|
||
- **Контекстная суб-панель** (`context action overlay`, далее — `context tray`) — плавающий контейнер над сценой для временных действий и параметров.
|
||
- **Вторичная панель** (`secondary editor panel`) — общее продуктовое назначение context tray: автоматический контекст либо явно открытая группа.
|
||
- **Launcher группы** — постоянная кнопка основной панели, открывающая семантически связанную группу второго уровня.
|
||
- **Group tray** — явно открытый вариант вторичной панели с инструментами или командами launcher-а.
|
||
- **Параметры инструмента** — элементы, которые относятся только к активному инструменту: толщина новой стены, заливка фигуры и подобные настройки.
|
||
- **Палитра инструмента** — крупный временный набор вариантов, например палитра мебели.
|
||
- **Сцена** — рабочая область плана под панелями редактора.
|
||
- **Контекст** — текущее выделение, незавершённая операция, активный инструмент и доступные для них действия.
|
||
|
||
## 5. Цели
|
||
|
||
1. Полностью исключить изменение геометрии сцены при выборе и снятии выбора.
|
||
2. Сохранить кнопку выхода в постоянной позиции.
|
||
3. Сделать связь действий с выбранным объектом очевидной.
|
||
4. Ввести один масштабируемый паттерн для всех редакторов.
|
||
5. Не допустить возврата проблемы при добавлении новых контекстных кнопок.
|
||
6. Сохранить прямой доступ к частым контекстным действиям объекта; второй уровень использовать для осознанных групп инструментов, а не для сокрытия «Свойства» и «Удалить».
|
||
7. Поддержать длинные RU/EN-подписи и узкие Lovelace-карточки без горизонтального скролла всей страницы.
|
||
8. Сохранить доступность с клавиатуры и `prefers-reduced-motion`.
|
||
9. Подготовить безопасную основу для группировки нескольких связанных кнопок под одним launcher без увеличения основной панели.
|
||
|
||
## 6. Не входит в задачу
|
||
|
||
- изменение команд редактирования и их бизнес-логики;
|
||
- изменение command stack, Undo/Redo или правил подтверждения удаления;
|
||
- изменение схемы данных, миграций или backend API;
|
||
- новая модель выделения нескольких объектов;
|
||
- автоматическое перемещение выбранного объекта из-под суб-панели;
|
||
- полноценный touch-паритет редакторов;
|
||
- перенос действий режима просмотра в editor context tray;
|
||
- изменение состава инструментов редакторов;
|
||
- автоматическое сворачивание частых действий в меню `…`.
|
||
- вложенные подменю третьего и последующих уровней;
|
||
- автоматическая группировка несвязанных действий только ради экономии ширины;
|
||
- превращение всех основных инструментов в скрытые группы без отдельного UX-решения.
|
||
|
||
## 7. Информационная архитектура панелей
|
||
|
||
### 7.1. Три уровня интерфейса
|
||
|
||
| Уровень | Содержимое | Участвует в высоте сцены | Может появляться динамически |
|
||
|---|---|---:|---:|
|
||
| Основная панель | Инструменты редактора, Undo/Redo, постоянные настройки, выход | Да | Нет в пределах одного редактора |
|
||
| Контекстная суб-панель | Действия выбранного объекта, временные команды, компактные параметры инструмента | Нет | Да |
|
||
| Крупная палитра | Мебель и будущие большие каталоги вариантов | Нет | Да |
|
||
|
||
Крупная палитра использует тот же overlay-host, но отдельный вариант компоновки. Она не должна превращать компактную строку «Свойства / Удалить» в высокий каталог и не должна участвовать в измерении header.
|
||
|
||
### 7.2. Нормативное правило для новых функций
|
||
|
||
Новый элемент нельзя добавлять в flow основной панели, если хотя бы одно из условий истинно:
|
||
|
||
- он появляется только после выделения объекта;
|
||
- он исчезает после снятия выделения;
|
||
- он доступен только во время незавершённого жеста;
|
||
- он относится только к одному временному типу контекста;
|
||
- его появление потенциально меняет число строк основной панели.
|
||
|
||
Такой элемент обязан использовать context tray или tool-palette overlay.
|
||
|
||
Это правило распространяется не только на кнопки. Иначе тот же дефект вернётся через динамический input, checkbox, hint или picker. При этом постоянный контрол может оставаться в основной панели, если его наличие не зависит от выделения и он предусмотрен компоновкой активного редактора.
|
||
|
||
Контрол, состав или видимость которого зависят от **активного инструмента**, считается transient tool parameter и всегда размещается во вторичной панели. «Зарезервированная стабильная зона» внутри primary toolbar не используется: она либо отнимает место у всех остальных инструментов, либо снова создаёт разные правила между редакторами. В основной панели остаются только controls, доступные при любом инструменте данного редактора.
|
||
|
||
### 7.3. Группы инструментов как второй уровень
|
||
|
||
Группировка является осознанной информационной архитектурой, а не автоматической реакцией на нехватку места.
|
||
|
||
Группу можно создавать, если одновременно выполнены условия:
|
||
|
||
- в ней не менее двух действий/инструментов;
|
||
- элементы решают одну понятную пользовательскую задачу;
|
||
- группа имеет короткое самостоятельное имя и различимую иконку;
|
||
- пользователю не требуется видеть все элементы группы одновременно с геометрией сцены;
|
||
- скрытие вариантов за одним дополнительным click/tap дешевле, чем постоянное переполнение основной панели.
|
||
|
||
Нельзя объединять в одну группу несвязанные команды вроде «Удалить», «Толщина», «Undo» и «Добавить устройство». Опасные действия selection не переносятся в launcher-группу только ради экономии места: они остаются в автоматически появляющемся контексте выбранного объекта.
|
||
|
||
Для первой версии вторичного уровня действует ограничение:
|
||
|
||
- только один уровень раскрытия;
|
||
- только одна открытая группа одновременно;
|
||
- рекомендуемый размер группы — 2–6 элементов;
|
||
- более 6 элементов используют palette-вариант или требуют отдельного UX-review;
|
||
- вложенный launcher внутри group tray запрещён.
|
||
|
||
### 7.4. Взаимодействие автоматического контекста, явной группы и палитры
|
||
|
||
Вторичная поверхность физически одна. Приоритет её содержимого:
|
||
|
||
1. явно открытая пользователем поверхность — группа либо палитра; если пользователь открывает другую явную поверхность, действует последняя;
|
||
2. автоматические actions выбранного объекта;
|
||
3. параметры активного инструмента;
|
||
4. краткая статусная подсказка.
|
||
|
||
Открытая группа или палитра временно заменяет автоматический контекст, но не уничтожает selection. После закрытия явной поверхности актуальный автоматический контекст вычисляется заново и возвращается. Нельзя хранить и восстанавливать старый DOM-снимок: выбранный объект мог измениться или исчезнуть.
|
||
|
||
Группа и палитра взаимно исключают друг друга. Открытие группы закрывает палитру; открытие палитры закрывает группу. Палитра использует те же правила `Escape`, смены editor/space и потери launcher, что и группа. Click/tap вне палитры по умолчанию закрывает её и поглощается; palette-вариант для повторного размещения может явно получить политику `stay-open-on-canvas`, при которой валидный click сцены выполняет размещение, а не считается dismiss.
|
||
|
||
Если действие группы или палитры само меняет tool/selection, после закрытия показывается уже новый вычисленный контекст.
|
||
|
||
## 8. Компоновка
|
||
|
||
### 8.1. Схема
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ [Постоянные инструменты ……………………] [Undo] [Redo] │ [Закрыть] │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
┌───────────────────────────────┐
|
||
│ Объект [Свойства] [Удалить] │
|
||
└───────────────────────────────┘
|
||
┌──────────────────────────── рабочая сцена ──────────────────────────┐
|
||
│ Суб-панель находится над этой областью и не меняет её размеры. │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 8.2. Основная панель
|
||
|
||
Основная панель делится на две независимые зоны:
|
||
|
||
1. **tool rail** — инструменты и постоянные контролы;
|
||
2. **pinned end cap** — кнопка «Закрыть редактор».
|
||
|
||
Требования:
|
||
|
||
- `pinned end cap` не участвует в переносе элементов tool rail;
|
||
- кнопка закрытия имеет постоянную доступную область не менее `40 × 40 px`, рекомендуемая — `44 × 44 px`;
|
||
- между tool rail и кнопкой закрытия допускается вертикальный divider;
|
||
- фон end cap непрозрачен либо достаточно плотный, чтобы под него не просвечивали прокручиваемые инструменты;
|
||
- на узкой ширине прокручивается или адаптируется tool rail, а не кнопка закрытия;
|
||
- выбор объекта не меняет `getBoundingClientRect()` кнопки закрытия более чем на `1 px` из-за округления.
|
||
|
||
### 8.3. Контекстная суб-панель
|
||
|
||
Рекомендуемое положение — под основной панелью, с выравниванием к логическому концу (`inline-end`). На ширине, где это невозможно, панель занимает доступную ширину с одинаковыми внешними отступами.
|
||
|
||
| Параметр | Требование |
|
||
|---|---|
|
||
| Позиционирование | Абсолютное относительно stage shell или отдельного overlay-host; вне layout-flow |
|
||
| Отступ от основной панели | `var(--sp-2)` / 4 px |
|
||
| Отступ от края карточки | не менее `var(--sp-4)` / 8 px |
|
||
| Внутренний padding | `var(--sp-3) var(--sp-4)` / 6 × 8 px |
|
||
| Gap | `var(--sp-3)` / 6 px |
|
||
| Радиус | `var(--rad-l)` / 12 px |
|
||
| Граница | 1 px `var(--hp-line)` с повышенной видимостью на сложном фоне |
|
||
| Тень | `var(--shadow-2)` |
|
||
| Максимальная ширина | `calc(100% - 2 * var(--sp-4))` |
|
||
| Z-index | выше сцены и её интерактивных объектов, ниже меню, picker, toast и dialog |
|
||
|
||
Поверхность:
|
||
|
||
- базовый фон строится от `var(--hp-bg)` / `--card-background-color` через отдельные theme tokens;
|
||
- рекомендуемый default `--hp-editor-tray-bg` соответствует приблизительно **86% непрозрачности** базового фона;
|
||
- `--hp-editor-tray-blur` по умолчанию равен `8px`, но blur не является обязательным;
|
||
- fallback без `backdrop-filter` использует отдельный `--hp-editor-tray-bg-fallback`, по умолчанию приблизительно 94% базового фона;
|
||
- компонент не содержит собственных hardcoded `rgba()`/процентов: все значения поверхности задаются токенами и могут быть переопределены темой;
|
||
- opacity нельзя применять ко всему контейнеру: текст, иконки и focus ring остаются полностью непрозрачными;
|
||
- контраст текста и controls должен оставаться не ниже WCAG AA в светлой и тёмной теме, в том числе над Glow и яркой подложкой.
|
||
|
||
Минимальный набор новых design tokens:
|
||
|
||
| Токен | Default | Назначение |
|
||
|---|---|---|
|
||
| `--hp-editor-tray-bg` | производный от `--hp-bg` с визуальной плотностью около 86% | Фон при поддержке blur |
|
||
| `--hp-editor-tray-bg-fallback` | производный от `--hp-bg` с визуальной плотностью около 94% | Фон без `backdrop-filter` |
|
||
| `--hp-editor-tray-blur` | `8px` | Радиус backdrop blur |
|
||
| `--hp-editor-tray-border` | производный от `--hp-line` | Граница над сложным фоном |
|
||
| `--hp-editor-tray-shadow` | `var(--shadow-2)` | Elevation вторичной панели |
|
||
|
||
### 8.4. Порядок элементов
|
||
|
||
1. необязательная компактная метка типа контекста (`Перегородка`, `Колонна`, `Линия`), с обрезкой ellipsis;
|
||
2. недеструктивные параметры;
|
||
3. основное действие «Свойства»;
|
||
4. другие недеструктивные действия;
|
||
5. деструктивное действие «Удалить» — последним.
|
||
|
||
Метка контекста не должна вытеснять действия. На узкой ширине она скрывается первой, а её текст остаётся доступен через `aria-label` панели.
|
||
|
||
### 8.5. Launcher и явно открытая группа
|
||
|
||
Launcher находится в tool rail основной панели и визуально отличается от обычной one-shot команды:
|
||
|
||
- постоянная локализованная подпись группы;
|
||
- основная иконка группы;
|
||
- небольшой chevron раскрытия;
|
||
- `aria-expanded="true|false"` и `aria-controls`;
|
||
- активное оформление, если group tray открыт либо один из её persistent tools активен.
|
||
|
||
Подпись launcher не меняется на название последнего выбранного дочернего инструмента: стабильное имя группы лучше поддерживает узнаваемость. Активный дочерний инструмент показывается внутри group tray, а в tooltip launcher допустима строка вида «Стены — активна Перегородка».
|
||
|
||
Group tray на широком экране привязывается к началу launcher и сдвигается/clamp-ится внутрь карточки, если у края недостаточно места. Selection tray по-прежнему выравнивается к `inline-end`. На узком экране оба варианта используют почти полную доступную ширину.
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ [Группа ▾] [Другой tool] ……………………………… [Undo] │ [Закрыть] │
|
||
└─────┬────────────────────────────────────────────────────────────────┘
|
||
└─┌────────────────────────────────────────┐
|
||
│ [Вариант A] [Вариант B] [Вариант C] │ ← group tray
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
Клик по launcher только открывает/закрывает группу и сам по себе не повторяет «последний использованный» инструмент. Split-button в это ТЗ не входит: две зоны на одной компактной кнопке хуже обнаруживаются и сложнее на touch.
|
||
|
||
## 9. Адаптивное поведение
|
||
|
||
Адаптация определяется **шириной карточки/контейнера**, а не только шириной окна браузера.
|
||
|
||
| Ширина контейнера | Context tray |
|
||
|---|---|
|
||
| `>= 900 px` | Компактная строка по содержимому, выровнена к `inline-end` |
|
||
| `560–899 px` | Строка до доступной ширины; внутренние группы могут переноситься внутри overlay |
|
||
| `< 560 px` | Отступ 8 px с обеих сторон; actions остаются одной прокручиваемой по горизонтали строкой |
|
||
|
||
Требования для узкого режима:
|
||
|
||
- горизонтальная прокрутка допустима только внутри action rail;
|
||
- карточка и страница не получают горизонтальный scrollbar;
|
||
- «Свойства» и «Удалить» не сжимаются до нечитаемой ширины;
|
||
- деструктивная кнопка не скрывается за clip без возможности прокрутки;
|
||
- появление второй строки внутри крупной палитры разрешено, потому что палитра перекрывает сцену, а не меняет её размер;
|
||
- безопасная зона и системные inset учитываются, если карточка показана fullscreen/kiosk.
|
||
|
||
Основная панель может изменить базовую высоту при реальном resize контейнера или смене редактора. Это отдельное осознанное событие. Выделение объекта при неизменной ширине не может быть причиной её перераскладки.
|
||
|
||
Group tray следует за общими breakpoint-правилами. Он не выходит за границы карточки, даже если launcher находится у её правого края; при нехватке места позиция clamp-ится, а не создаёт page overflow. Связь с launcher сохраняется визуально через выравнивание, а не обязательную стрелку-указатель.
|
||
|
||
## 10. Анимация
|
||
|
||
### Появление
|
||
|
||
- длительность: `140 ms`;
|
||
- opacity: `0 → 1`;
|
||
- transform: `translateY(-4px) → translateY(0)`;
|
||
- easing: `cubic-bezier(0.2, 0.7, 0.2, 1)`.
|
||
|
||
### Исчезновение
|
||
|
||
- длительность: `100 ms`;
|
||
- opacity: `1 → 0`;
|
||
- transform: `translateY(0) → translateY(-2px)`;
|
||
- действия становятся недоступны сразу после потери контекста, даже если визуальный exit ещё заканчивается.
|
||
|
||
### Смена выбранного объекта
|
||
|
||
- внешний контейнер не исчезает, если следующий валидный контекст уже существует;
|
||
- содержимое меняется коротким crossfade до `100 ms`;
|
||
- недопустимо проигрывать закрытие и повторное открытие панели между двумя последовательными выделениями;
|
||
- текущая анимация отменяется при поступлении более нового состояния.
|
||
|
||
### Открытие группы
|
||
|
||
- используется та же enter/exit-анимация вторичной панели;
|
||
- переключение с автоматического контекста на group tray выполняется одним crossfade без закрытия overlay-host;
|
||
- открытие другой группы заменяет текущую, не создавая второй панели;
|
||
- после выбора persistent tool группа закрывается, а tool-parameters появляются отдельным crossfade;
|
||
- launcher не меняет положение и не вызывает reflow основной панели.
|
||
|
||
### Reduced motion
|
||
|
||
При `prefers-reduced-motion: reduce` transform и изменение размеров не используются. Допустима мгновенная смена либо opacity не более `80 ms`.
|
||
|
||
## 11. Состояния и переходы
|
||
|
||
| Состояние | Условие | Поведение панели |
|
||
|---|---|---|
|
||
| `hidden` | Нет временного контекста | Панель отсутствует и не перехватывает pointer |
|
||
| `selection` | Выбран редактируемый объект | Показаны действия выбранного типа |
|
||
| `operation` | Есть незавершённая операция с доступной отменой/сбросом | Показаны действия операции |
|
||
| `tool-parameters` | Активному инструменту нужны непостоянные параметры | Показаны параметры инструмента |
|
||
| `selection+tool` | Одновременно нужны параметры и действия объекта | Один tray с двумя группами, а не две панели |
|
||
| `group` | Пользователь явно открыл launcher основной панели | Автоматический контекст временно заменён содержимым группы |
|
||
| `palette` | Активна крупная палитра | Overlay-host использует расширенный palette-вариант |
|
||
| `blocked` | Команда временно недоступна | Кнопка остаётся видимой disabled с объяснением |
|
||
| `exiting` | Контекст потерян | Панель inert, заканчивает exit-анимацию |
|
||
|
||
Одновременно может существовать только один активный context tray. Приоритет UI-слоёв:
|
||
|
||
1. dialog;
|
||
2. menu / picker / popover;
|
||
3. toast;
|
||
4. editor context tray / tool palette;
|
||
5. hover-tooltip устройств (`_tip`), geometry hints и другие scene-owned overlays;
|
||
6. сцена.
|
||
|
||
Внутри одного overlay-host состояния `group` и `palette` являются взаимно исключающими явными поверхностями и имеют приоритет над `selection`; последнее явно открытое состояние заменяет предыдущее. Dialog/popover всегда выше группы и палитры.
|
||
|
||
Существующий `_tip` не может перекрывать tray. Если вторичная панель появляется поверх уже открытого tooltip, tooltip остаётся на scene-уровне и визуально обрезается/оказывается под tray; вход pointer в интерактивную поверхность tray должен очищать stale hover-tooltip, чтобы он не возник повторно без нового hover сцены.
|
||
|
||
## 12. Матрица существующих редакторов
|
||
|
||
### 12.1. Редактор плана
|
||
|
||
| Текущий динамический элемент | Новое место | Примечание |
|
||
|---|---|---|
|
||
| «Свойства» выбранной перегородки/колонны/незавершённого контура | Context tray | Недеструктивное действие перед Delete |
|
||
| «Удалить» выбранный объект | Context tray | Деструктивное действие последним; текущее подтверждение сохраняется |
|
||
| «Сбросить» незавершённый контур | Context tray | Контекст операции, не постоянный инструмент |
|
||
| Толщина для «Контур комнаты» / «Перегородка» / «Колонна» | Tool-parameters в том же tray | Не должна менять строки основной панели при переключении контекста |
|
||
| Динамическая текстовая подсказка инструмента | Статусная область tray либо overlay-hint сцены | Не должна входить в flow основной панели |
|
||
| Undo / Redo | Основная панель | Постоянные действия |
|
||
| «Закрыть редактор» | Pinned end cap основной панели | Никогда не перемещается из-за selection |
|
||
|
||
### 12.2. Редактор устройств
|
||
|
||
Текущие постоянные «Добавить», «Показать скрытые», «Правила», Undo/Redo и выход остаются в основной панели, если их доступность не зависит от выделенного marker.
|
||
|
||
Любые будущие quick actions выбранного устройства — «Свойства», «Скрыть», «Удалить», «Открыть в HA» и подобные — обязаны появляться в context tray. Нельзя добавлять их в общую строку рядом с выходом.
|
||
|
||
Если marker открывает dialog непосредственно двойным кликом, это не отменяет возможность явной кнопки «Свойства» в tray, но состав действий должен быть единым resolver-ом, а не двумя независимыми списками.
|
||
|
||
### 12.3. Редактор подложки
|
||
|
||
| Элемент | Новое место |
|
||
|---|---|
|
||
| Свойства выбранного объекта | Context tray |
|
||
| Удаление выбранного объекта | Context tray |
|
||
| Динамическая заливка, применимая только к выбранной фигуре/активному tool | Tool-parameters tray |
|
||
| «Сбросить подложку», появляющееся только после сдвига | Context tray |
|
||
| Цвет, толщина и другие параметры конкретного активного инструмента | Tool-parameters tray; зарезервированная зона primary toolbar не используется |
|
||
| Палитра мебели | Отдельный `palette`-вариант того же overlay-host |
|
||
|
||
Палитра мебели может быть выше компактной панели и иметь собственный scroll, но не уменьшает сцену. Закрытие палитры не меняет zoom/pan.
|
||
|
||
### 12.4. Будущие редакторы
|
||
|
||
Каждый редактор предоставляет декларативный набор:
|
||
|
||
- постоянных toolbar items;
|
||
- контекстных actions;
|
||
- компактных context controls;
|
||
- при необходимости — одного palette overlay.
|
||
|
||
Редактор не должен вручную вставлять transient-кнопки в primary toolbar.
|
||
|
||
Он также может декларативно предоставить группы основной панели. Это разрешает в будущем заменить несколько близких инструментов одной кнопкой без изменения overlay-архитектуры. Конкретный состав каждой группы утверждается отдельным UX-решением: это ТЗ создаёт механизм, но не объединяет существующие кнопки автоматически.
|
||
|
||
## 13. Поведение в ключевых сценариях
|
||
|
||
### Выделение и снятие выделения
|
||
|
||
- выделение завершено → action model вычислена → tray появляется;
|
||
- клик по пустой сцене снимает выделение по существующим правилам → tray исчезает;
|
||
- появление tray не вызывает auto-pan и не пытается отодвинуть объект;
|
||
- если объект находится под tray, пользователь может продолжить pan/zoom; система не меняет положение плана сама.
|
||
|
||
### Drag и resize
|
||
|
||
- новый tray не должен перехватывать завершение pointer-жеста;
|
||
- если выделение создаётся на `pointerdown`, actions становятся интерактивны только после commit/`pointerup` текущего жеста;
|
||
- при drag уже выбранного объекта tray может оставаться видимым, но его состав не пересчитывается на каждом `pointermove`;
|
||
- screen-to-plan mapping не меняется в середине жеста.
|
||
|
||
### Dialog свойств
|
||
|
||
- открытие dialog делает tray inert и помещает его под scrim;
|
||
- после закрытия без удаления прежнее выделение и tray восстанавливаются;
|
||
- после сохранения tray отражает актуальный тип/доступность действий;
|
||
- если dialog удалил или заменил объект, stale actions не остаются.
|
||
|
||
### Delete, Undo и Redo
|
||
|
||
- подтверждение удаления остаётся обязательным там, где оно уже предусмотрено;
|
||
- после подтверждённого удаления выделение очищается до следующего render actions;
|
||
- Undo, восстановивший объект, не обязан автоматически выделять его, если текущий command stack этого не делает;
|
||
- Redo/внешнее обновление, удалившее выделенный объект, безопасно скрывает tray;
|
||
- `Delete` остаётся удалением выбранного объекта, только когда focus находится на сцене/editor host и действуют существующие правила выбранного объекта;
|
||
- при focus внутри **любой** вторичной панели — selection tray, tool-parameters, group или palette — глобальные `Delete`/`Backspace` не удаляют объект сцены; для удаления используется явная кнопка либо focus сначала возвращается на сцену;
|
||
- нативное поведение редактируемых input/textarea/contenteditable внутри панели всегда имеет приоритет над editor shortcuts.
|
||
|
||
### Смена editor/space
|
||
|
||
- при смене редактора или пространства старый tray немедленно становится inert;
|
||
- context state старого редактора не может мигнуть поверх нового;
|
||
- анимация смены editor chrome продолжает учитывать только постоянную основную панель;
|
||
- overlay нового редактора появляется после активации его актуального контекста.
|
||
|
||
### Быстрая смена выделения
|
||
|
||
- используется последний context state;
|
||
- старые callbacks не могут удалить или изменить новый выбранный объект;
|
||
- незавершённая exit-анимация отменяется;
|
||
- не допускается накопление нескольких overlay в DOM.
|
||
|
||
### Явная группа кнопок
|
||
|
||
- click/tap по launcher открывает group tray; повторный click/tap закрывает его;
|
||
- группа никогда не открывается по hover;
|
||
- открытие нового launcher закрывает предыдущую группу;
|
||
- `Escape` закрывает group tray, не снимает selection и возвращает focus launcher-у;
|
||
- click/tap вне явно открытой группы закрывает её; dismiss-click поглощается и не должен одновременно рисовать, удалять или выделять объект на сцене;
|
||
- после закрытия заново вычисляется и показывается актуальный automatic context;
|
||
- выбор дочернего persistent tool активирует tool и по умолчанию закрывает группу;
|
||
- one-shot command закрывает группу перед выполнением команды;
|
||
- toggle/setting может явно получить политику `stay-open`, если пользователю разумно изменить несколько соседних параметров;
|
||
- на group tray распространяется общее правило вторичных панелей: `Delete`/`Backspace` не удаляют выбранный объект сцены;
|
||
- действие, открывающее dialog, закрывает group tray до появления dialog;
|
||
- если group action стала недоступна после открытия, она остаётся видимой disabled с актуальным объяснением;
|
||
- переключение editor/space немедленно закрывает группу без восстановления в новом контексте.
|
||
|
||
### Явная палитра
|
||
|
||
- палитра имеет тот же верхний приоритет над автоматическим контекстом, что и явно открытая группа;
|
||
- opening palette закрывает открытую группу, opening group закрывает палитру;
|
||
- изменение selection через keyboard, Undo/Redo или external config не закрывает палитру само по себе, но underlying context пересчитывается и будет показан только после её закрытия;
|
||
- обычный dismiss-click вне палитры закрывает её и не проваливается в сцену;
|
||
- palette с политикой `stay-open-on-canvas` принимает валидные canvas-clicks активного инструмента для повторного размещения; невалидный внешний click закрывает палитру и поглощается;
|
||
- `Escape`, смена editor/space, потеря видимого launcher и открытие dialog закрывают палитру;
|
||
- после закрытия палитры automatic context строится заново, без восстановления stale DOM.
|
||
|
||
## 14. Архитектурный контракт
|
||
|
||
Рекомендуется единый компонент/renderer уровня editor shell, условно `hp-editor-context-tray`.
|
||
|
||
Он принимает декларативную модель, а не знает о геометрии конкретного редактора:
|
||
|
||
```ts
|
||
type EditorContextAction = {
|
||
id: string;
|
||
label: string;
|
||
icon: string;
|
||
tone?: "normal" | "primary" | "danger";
|
||
disabled?: boolean;
|
||
disabledReason?: string;
|
||
shortcut?: string;
|
||
invoke: () => void;
|
||
};
|
||
|
||
type EditorContextModel = {
|
||
contextId: string;
|
||
kind: "selection" | "operation" | "tool" | "mixed" | "group" | "palette";
|
||
accessibleLabel: string;
|
||
visibleLabel?: string;
|
||
actions: EditorContextAction[];
|
||
controls?: unknown;
|
||
};
|
||
|
||
type EditorToolbarGroup = {
|
||
id: string;
|
||
label: string;
|
||
icon: string;
|
||
activeItemId?: string;
|
||
items: Array<EditorContextAction & {
|
||
role?: "tool" | "command" | "toggle";
|
||
closePolicy?: "on-activate" | "stay-open";
|
||
}>;
|
||
};
|
||
```
|
||
|
||
Точные TypeScript-типы могут отличаться, но обязательны следующие свойства архитектуры:
|
||
|
||
1. один host на активный editor shell;
|
||
2. один источник истины для доступных действий;
|
||
3. отсутствие editor-specific transient DOM внутри primary toolbar;
|
||
4. context tray находится вне элемента, чья высота входит в `_hdrH`;
|
||
5. stage shell является positioning context для overlay;
|
||
6. pointer-events отключены на пустой оболочке overlay и включены только на самой панели;
|
||
7. stale action проверяет актуальность `contextId` перед мутацией;
|
||
8. dialog, keyboard shortcut и tray вызывают одну и ту же команду.
|
||
9. launcher-группы и автоматический контекст используют один overlay-host и общую систему позиционирования;
|
||
10. явное состояние `openGroupId` не записывается в конфигурацию и сбрасывается при смене редактора/пространства.
|
||
|
||
### Правило формирования `contextId`
|
||
|
||
`contextId` формируется детерминированно из:
|
||
|
||
1. активного editor mode;
|
||
2. `kind` контекста;
|
||
3. стабильного ID объекта, операции, инструмента, группы или палитры;
|
||
4. монотонного `configEpoch`, меняющегося при замене config, пространства или исходной коллекции объектов;
|
||
5. локальной revision контекста, если доступность actions изменилась без смены стабильного ID.
|
||
|
||
Рекомендуемый формат: `editor:<mode>:<kind>:<targetId>:<configEpoch>:<revision>`. Для явно открытой группы используется её `groupId` и generation конкретного открытия. Индекс объекта в массиве, DOM-index и локализованная подпись не могут быть частью identity: они нестабильны.
|
||
|
||
Перед выполнением action host сравнивает сохранённый `contextId` с актуальным. При несовпадении команда не выполняется, старый tray становится inert и запускается пересчёт текущей модели.
|
||
|
||
### Обязательная граница измерений
|
||
|
||
`ResizeObserver`, определяющий высоту header/editor chrome, **не должен наблюдать context tray и tool palette**. Появление, изменение текста, локализации или перенос строки внутри overlay не может изменить `_hdrH`.
|
||
|
||
## 15. Доступность
|
||
|
||
- контейнер действий имеет `role="toolbar"` и локализованный `aria-label`, например «Действия: Перегородка»;
|
||
- кнопки остаются нативными button-элементами с видимым focus ring;
|
||
- появление панели после клика не перехватывает focus;
|
||
- для клавиатурного выбора допускается `aria-live="polite"` с коротким сообщением о выбранном типе, но не с перечислением всех кнопок;
|
||
- Tab включает действия tray в предсказуемом DOM-порядке;
|
||
- если сфокусированная кнопка исчезла вследствие Delete/deselect, focus возвращается на сцену или её устойчивый editor host, а не на `body`;
|
||
- `Escape` сначала закрывает верхний dialog/popover, затем по существующим правилам отменяет операцию или снимает выделение;
|
||
- disabled action сообщает причину доступным текстом/tooltip и не зависит только от цвета;
|
||
- danger-состояние различается текстом/иконкой, а не одним красным цветом;
|
||
- `prefers-reduced-motion` соблюдается;
|
||
- контраст проверяется над светлой, тёмной, цветной и Glow-подложкой.
|
||
|
||
Для launcher-группы:
|
||
|
||
- launcher является `button`, имеет `aria-expanded`, `aria-controls` и локализованное описание группы;
|
||
- сам group tray использует `role="toolbar"`, а не ARIA `menu`, поскольку содержит инструменты и toggle-состояния, а не классическое application menu;
|
||
- `Enter`/`Space` открывают или закрывают группу;
|
||
- `ArrowDown` на закрытом launcher открывает группу и переводит focus на активный либо первый доступный item;
|
||
- внутри группы действуют `Left/Right`, `Home/End` и roving tabindex;
|
||
- `Escape` закрывает группу и возвращает focus launcher-у;
|
||
- при открытии мышью focus может остаться на launcher, а следующий `Tab` входит в группу;
|
||
- disabled items пропускаются стрелочной навигацией, но остаются доступны screen reader вместе с причиной;
|
||
- после выбора item focus возвращается на launcher либо на сцену активированного инструмента согласно типу действия, но никогда не теряется на `body`.
|
||
|
||
## 16. Touch-статус
|
||
|
||
Редакторы House Plan остаются **desktop-first / best effort на touch** согласно `TOUCH-SUPPORT.md`. Полноценный паритет сложного редактирования не является целью задачи.
|
||
|
||
При этом обязательный safety floor сохраняется:
|
||
|
||
- tray не должен появляться из-за pinch/multi-touch misclick;
|
||
- доступные на touch действия имеют hit-area не менее `44 × 44 px`;
|
||
- pinch/pan вне панели не блокируется её прозрачной оболочкой;
|
||
- нажатие на action не проваливается в сцену;
|
||
- выход из редактора всегда доступен;
|
||
- горизонтальная прокрутка actions не должна случайно перемещать объект плана.
|
||
- launcher открывает группу только по явному tap, не по hover/long-press;
|
||
- dismiss-tap по сцене не проваливается в геометрическую команду.
|
||
|
||
## 17. Локализация и copy
|
||
|
||
Минимальный набор строк поверхности; существующие ключи переиспользуются, новые создаются только при отсутствии эквивалента:
|
||
|
||
| RU | EN | Назначение |
|
||
|---|---|---|
|
||
| `Действия: {object}` | `Actions: {object}` | `aria-label` tray |
|
||
| `Параметры инструмента: {tool}` | `Tool options: {tool}` | `aria-label` tool tray |
|
||
| `Свойства` | `Properties` | Основное действие объекта |
|
||
| `Удалить` | `Delete` | Деструктивное действие |
|
||
| `Открыть группу {group}` | `Open {group} group` | Доступное описание launcher при необходимости |
|
||
| `{group}, активен {item}` | `{group}, {item} active` | Tooltip/ARIA active-child состояния |
|
||
|
||
Существующие подписи переиспользуются, если их семантика совпадает. Текст не собирается конкатенацией, нарушающей порядок слов в других языках.
|
||
|
||
Для «Свойства» и «Удалить» обязательно переиспользуются существующие ключи `btn.properties` и `btn.delete` из `src/i18n/ru.json` и `src/i18n/en.json`; дублирующие tray-specific ключи не создаются. Новые строки добавляются только для aria-label самой панели и launcher-групп.
|
||
|
||
## 18. Эдж-кейсы
|
||
|
||
| № | Ситуация | Нормативное поведение | Основной раздел |
|
||
|---:|---|---|---|
|
||
| 1 | Карточка находится ровно на ширине прежнего переноса кнопок | Selection меняет только overlay; размеры сцены и primary toolbar остаются теми же с допуском 1 px | §1, §19 |
|
||
| 2 | Очень длинное название типа или локализованная подпись | Видимая context-метка получает ellipsis и скрывается первой; полное имя остаётся в ARIA; actions прокручиваются только внутри rail | §8.4, §9, §15 |
|
||
| 3 | Одновременно нужны tool parameters и actions объекта | Рендерится один tray с двумя семантическими группами; при дефиците ширины scroll/wrap происходит внутри overlay | §11 |
|
||
| 4 | Появилось более четырёх actions | Actions сохраняют приоритет и явную доступность; до шести допустим внутренний rail, свыше шести требуется отдельный UX-review, без автоматического overflow | §7.3 и правило ниже |
|
||
| 5 | Выбранный объект находится под overlay | Объект автоматически не перемещается; пользователь может pan/zoom, а overlay не меняет viewBox | §13 «Выделение» |
|
||
| 6 | Выбранный объект удалён external config update | `contextId` инвалидируется, actions немедленно становятся inert, selection очищается или пересчитывается | §13, §14 |
|
||
| 7 | Undo/Redo меняет тип или существование объекта | Context model строится заново; исчезнувший объект не остаётся selected, восстановленный не выбирается автоматически без текущего правила command stack | §13 «Delete, Undo и Redo» |
|
||
| 8 | Dialog открыт во время смены пространства | Старый tray становится inert и не восстанавливается в новом space; dialog закрывается/переназначается по существующим правилам смены пространства без stale callback | §13 «Смена editor/space» |
|
||
| 9 | Быстрые selection-clicks идут во время enter/exit | Предыдущая animation отменяется, применяется последний context state, в DOM остаётся один host | §10, §13 «Быстрая смена» |
|
||
| 10 | Pointer capture активен при появлении контекста | Actions не становятся интерактивными до commit/`pointerup`; mapping сцены не меняется | §13 «Drag и resize» |
|
||
| 11 | Из tool controls открыт color picker/popover | Picker находится выше tray; tray остаётся якорем и inert только в зоне взаимодействия picker; закрытие picker возвращает focus вызвавшему control | §11, §15 |
|
||
| 12 | `backdrop-filter` не поддерживается | Используется тематический fallback-токен более плотного фона; контент не меняет opacity | §8.3 |
|
||
| 13 | Light/dark, Glow или яркая картинка под tray | Используются theme tokens и проверенный AA-контраст; opacity не применяется ко всему контейнеру | §8.3, §19 |
|
||
| 14 | Browser zoom 125–200% | Позиция clamp-ится внутри карточки; page overflow отсутствует, controls сохраняют hit-area | §9, §15 |
|
||
| 15 | RTL-интерфейс | Применяются logical properties и `inline-start`/`inline-end`; порядок danger-action остаётся семантически последним | §8, §9 |
|
||
| 16 | Fullscreen/kiosk с safe-area inset | Внешние отступы overlay увеличиваются на соответствующий `env(safe-area-inset-*)`; tray не перекрывает системную область | §9 |
|
||
| 17 | Touch-scroll по action rail рядом со сценой | Gesture принадлежит rail, не двигает объект/план и не проваливается в сцену | §16 |
|
||
| 18 | `prefers-reduced-motion: reduce` | Transform отключён; используется мгновенная смена или opacity до 80 ms | §10 |
|
||
| 19 | Повторно выбран тот же объект без смены actions | Сохраняется тот же `contextId`; enter/crossfade не перезапускается | §10, §14 |
|
||
| 20 | Context инвалидирован между render и нажатием | Action повторно проверяет `contextId`, ничего не мутирует и инициирует пересчёт актуального контекста | §14 |
|
||
| 21 | Group launcher у края, tray не помещается | Group tray clamp-ится внутрь карточки без изменения ширины страницы | §8.5, §9 |
|
||
| 22 | Под group/palette изменился или исчез selection | Явная поверхность остаётся до штатного закрытия, underlying context пересчитывается; после закрытия stale selection не возвращается | §7.4, §13 |
|
||
| 23 | Две launcher-группы открыты быстрыми кликами | Последняя явная группа атомарно заменяет предыдущую; второй host не создаётся | §7.4, §13 |
|
||
| 24 | Активный дочерний tool стал недоступен | Active item снимается или становится disabled по актуальной модели; launcher не показывает ложное active-состояние | §8.5, §13 |
|
||
| 25 | В группе смешаны tool, command и toggle | Каждый item использует явные `role` и `closePolicy`; default — `on-activate`, `stay-open` разрешён только toggle/setting | §13, §14 |
|
||
| 26 | Dismiss-click попал поверх интерактивной геометрии | Group/palette закрывается, click поглощается и не выполняет selection/draw/delete; исключение — объявленная palette-политика `stay-open-on-canvas` | §13 |
|
||
| 27 | Launcher скрыт responsive layout или полностью ушёл из видимой области прокручиваемого tool rail | Связанная group/palette немедленно закрывается. Если focus был внутри неё или на ставшем невидимым launcher, focus переводится на ближайший видимый toolbar item, затем на toolbar host как fallback | §8.5, §9, §15 |
|
||
|
||
Если действий становится больше четырёх, они группируются по приоритету, но автоматически прятать их в overflow нельзя. Состав свыше шести видимых действий требует отдельного UX-review: это обычно признак слишком широкого контекста.
|
||
|
||
## 19. Критерии приёмки
|
||
|
||
### Геометрия
|
||
|
||
1. На каждой контрольной ширине высота и верхняя координата сцены до и после selection совпадают с допуском `1 px`.
|
||
2. `viewBox`, zoom и pan не меняются.
|
||
3. Кнопка закрытия остаётся в той же позиции с допуском `1 px`.
|
||
4. Появление tray не вызывает `ResizeObserver`-обновление `_hdrH`.
|
||
5. Страница и карточка не получают новый горизонтальный scrollbar.
|
||
|
||
### Поведение
|
||
|
||
1. «Свойства» открывает тот же dialog и тот же объект, что текущая команда.
|
||
2. «Удалить» сохраняет существующие правила подтверждения и command stack.
|
||
3. Снятие выделения скрывает actions.
|
||
4. Смена объектов обновляет actions без закрытия/открытия внешнего контейнера.
|
||
5. Смена редактора не оставляет stale overlay.
|
||
6. Только один tray существует одновременно.
|
||
7. Overlay не препятствует pan/zoom за пределами своей видимой поверхности.
|
||
8. Launcher открывает только одну группу и не меняет высоту основной панели.
|
||
9. Закрытие группы восстанавливает заново вычисленный automatic context.
|
||
10. Dismiss-click не проваливается в сцену.
|
||
11. Выбор дочернего tool отражается активным состоянием launcher без смены его постоянной подписи.
|
||
|
||
### Визуал и доступность
|
||
|
||
1. Поверхность читаема в светлой/тёмной теме и над Glow.
|
||
2. RU/EN помещаются на контрольных ширинах без page overflow.
|
||
3. Focus не теряется при появлении или удалении панели.
|
||
4. Все действия доступны с клавиатуры.
|
||
5. Reduced motion не использует transform-анимацию.
|
||
6. Touch safety floor соблюдён без требования полного editor parity.
|
||
|
||
## 20. План проверки при будущей реализации
|
||
|
||
### Автоматические проверки
|
||
|
||
1. Unit-тест resolver-а context actions для каждого редактора и типа selection.
|
||
2. Browser smoke: измерить `stage.getBoundingClientRect()` и кнопку закрытия до/после selection.
|
||
3. Browser smoke: быстрая смена двух объектов во время animation.
|
||
4. Browser smoke: Delete → Undo → Redo без stale actions.
|
||
5. Browser smoke: modal properties поверх tray.
|
||
6. Browser smoke: узкий контейнер без горизонтального scroll страницы.
|
||
7. Golden images для wide/medium/narrow, RU/EN, light/dark.
|
||
8. Reduced-motion сценарий.
|
||
9. Launcher: open/close, смена двух групп, выбор tool, one-shot command и `stay-open` toggle.
|
||
10. Selection tray → group tray → восстановление selection tray без stale object.
|
||
11. Keyboard matrix launcher/toolbar: Enter, Space, ArrowDown, Left/Right, Home/End, Escape, Tab.
|
||
12. Ревизия `demo/smoke_editor_tabs.mjs`: `editorSwapAnimatesHeight` продолжает проверять осознанную анимацию между редакторами, но selection/group/palette внутри одного редактора отдельно подтверждаются как layout-invariant.
|
||
13. Ревизия `demo/smoke_preloader_lifecycle.mjs` и связанных preloader smoke: новый overlay не входит в settle-измерения header, не продлевает boot veil и не воспринимается как поздний layout shift.
|
||
14. Регрессионная проверка существующих selectors `.editbar`, `.barclose` и `.editorchrome-inner` либо их осознанное обновление во всех smoke, использующих старую структуру панели.
|
||
|
||
### Рекомендуемая матрица ширин
|
||
|
||
- 420 px;
|
||
- 559 px;
|
||
- 560 px;
|
||
- 719/720/721 px как текущая чувствительная зона;
|
||
- 899/900 px;
|
||
- 1200 px.
|
||
|
||
На каждой ширине проверяются редактор плана, устройств и подложки минимум в состояниях `hidden`, `selection` и наиболее крупном контексте.
|
||
|
||
### Ручная проверка
|
||
|
||
- mouse: select, double click, drag, delete, properties;
|
||
- keyboard: Tab, Enter, Escape, Delete, Ctrl+Z/Ctrl+Shift+Z;
|
||
- touch best effort: single tap, action tap, горизонтальный scroll actions, pinch рядом с tray;
|
||
- browser zoom 100%, 125%, 200%;
|
||
- отсутствие визуального скачка при повторном выборе.
|
||
|
||
Полный прогон выполняется перед полноценным релизом; для beta — минимально необходимая матрица согласно принятому процессу проекта.
|
||
|
||
## 21. Последовательность будущей реализации
|
||
|
||
1. Добавить stage overlay-host, не входящий в измерение header.
|
||
2. Разделить primary toolbar на tool rail и pinned close end cap.
|
||
3. Создать общий декларативный context tray.
|
||
4. Сразу заложить в host явный `group`-режим и API launcher-групп, даже если первая миграция использует только selection actions.
|
||
5. Перенести selection actions редактора плана.
|
||
6. Перенести transient controls и operation actions редактора плана.
|
||
7. Подключить тот же контракт к редактору подложки и palette-варианту.
|
||
8. Подключить редактор устройств и зафиксировать API для будущих actions.
|
||
9. Добавить keyboard/focus semantics автоматического и явно открытого режимов.
|
||
10. Добавить геометрические browser assertions, launcher/group tests и golden fixtures.
|
||
11. Обновить `ARCHITECTURE.md`, `UX-MODES.md`, `DECOR-EDITOR.md`, `TOUCH-SUPPORT.md`, `TESTING.md`, RU/EN пользовательскую документацию и `CHANGELOG.md` / `CHANGELOG.ru.md`.
|
||
|
||
## 22. Зафиксированные допущения
|
||
|
||
1. Суб-панель выравнивается к правому/логическому краю, потому что там находятся object actions и выход; на узкой ширине становится почти полноширинной.
|
||
2. Правило распространяется на весь transient UI, а не только на кнопки. Это необходимо, чтобы такой же layout shift не вернулся через input, hint или picker.
|
||
3. Метка типа выбранного объекта рекомендуется, но скрывается первой при дефиците места.
|
||
4. Объект под overlay не перемещается автоматически.
|
||
5. Основная панель может менять высоту при смене редактора или resize контейнера, но не при selection/context changes внутри редактора.
|
||
6. Модель данных, команды и результаты редактирования остаются прежними.
|
||
7. Суб-панель проектируется как универсальный второй уровень editor chrome, а не как частный контейнер двух selection-кнопок.
|
||
8. Одна кнопка основной панели может в будущем открыть группу из 2–6 связанных инструментов/команд.
|
||
9. Группа открывается только явно и временно имеет приоритет над автоматическим контекстом; закрытие возвращает актуальный контекст.
|
||
10. Первый архитектурный этап не поддерживает вложенные группы и не объединяет существующие инструменты без отдельного продуктового решения.
|
||
|
||
Блокирующих продуктовых вопросов для начала реализации нет. Если владелец продукта хочет ограничить первый этап только кнопками «Свойства / Удалить», это допустимо как поэтапный rollout, но архитектурный запрет на transient UI в primary toolbar всё равно должен быть заложен сразу.
|