Многопанельная раскладка
Статус: Draft 1 (предпросмотр) — ранний доступ, не для продакшена. API управляемой раскладки выпущено, но ещё не обкатано на продуктовом потребителе. Имена полей, значения по умолчанию и правила валидации могут ещё меняться между минорными релизами. Закрепляйте точную версию на CDN, пока эта метка не снята. Практически для всех приложений рекомендуемым продуктовым режимом остаётся стандартный
compat— обращайтесь к управляемой раскладке только когда вам действительно нужно компоновать само обрамление.
Режим управляемой раскладки заменяет стандартное обрамление Wippy полностью декларативным деревом панелей. Вместо фиксированной оболочки с чатом и боковой панелью вы описываете дерево именованных панелей в YAML своего бэкенда. Web Host собирает раскладку при запуске, валидирует её и реактивно поддерживает во время выполнения. Панели можно изменять по размеру, сворачивать, менять местами, добавлять и удалять без перезагрузки страницы.
Когда использовать управляемую раскладку
Стандартный режим compat (по умолчанию) даёт вам фиксированный продукт Wippy: боковую панель навигации, панель чата, область страницы и правую панель артефактов. Это текущий, наиболее используемый продуктовый режим, и его достаточно практически для всех приложений.
Подключайте fe_mode = managed (ранний доступ) только когда вам нужно компоновать само обрамление:
| Потребность | Compat | Managed |
|---|---|---|
| Стандартные чат и навигация Wippy | Да | Заменяемы |
| Несколько слотов страниц рядом | Нет | Да |
| Пользовательская боковая панель или компонент-координатор | Ограниченно | Да — любой вид панели |
| Адаптивные раскладки по брейкпойнтам | Нет | Да |
| Плавающие панели-оверлеи | Нет | Да |
| Headless-компонент-координатор | Нет | Да (coordinators) |
| Маршрутизация с учётом URL на каждую панель | Только главная панель | Каждая панель kind: page |
| Шина сообщений между панелями | Нет | Да (broadcast/send/on) |
Совместимость
Управляемая раскладка охватывает Web Host, фасад и несколько пакетов @wippy-fe/*. Используйте одно совместимое семейство пакетов для конкретного целевого релиза Web Host и проверяйте раздаваемую им import map; не смешивайте версии пакетов из несвязанных релизов.
Карта релизов
| Релиз | Дополнения управляемой раскладки |
|---|---|
Web Host 1.0.50, Wippy FE 0.0.50 |
Типизированные compat-намерения, @HOST/compat-coordinator, синхронизация URL браузера и кнопок «Назад»/«Вперёд», встроенные вкладки панелей, привязанные плавающие панели и useSwapBuffer(). |
Web Host 1.0.51, Wippy FE 0.0.51 |
Реактивное и безопасное к гонкам управление сессией/токеном <wippy-chat>, опциональные темизированные рукоятки разделителей, ограничения размера только по оси разделения, исправления геометрии/наложения выдвижных панелей и упакованная source map прокси. |
Web Host 1.0.52, Wippy FE 0.0.52 |
Типизированная видимость сохраняемых WC и useHostVisibilityRefresh(), немедленная готовность страницы вместо ожидания 14-секундного запасного срока, отклонение устаревших ключей рендерера, обновление props компонентов на месте и изолированный слой разделителей с --wippy-layout-splitter-z-index. |
14-секундный показ страницы — запасной механизм Web Host 1.0.52, а не функция 1.0.51
и не задержка загрузки приложения. Размеры по оси разделения и реактивный чат
появились в 1.0.51; сохраняемая видимость, готовность по ключу и слоение разделителей
появились в 1.0.52.
Сохраняемая видимость прямых веб-компонентов требует Web Host 1.0.52 и
@wippy-fe/webcomponent-core, @wippy-fe/webcomponent-vue и
@wippy-fe/shared версии 0.0.52. Более ранние релизы управляемой раскладки не предоставляют
типизированный контракт data-wippy-visible или useHostVisibilityRefresh().
Сохраняемая активность веб-компонентов
Управляемые раскладки держат панели смонтированными при подменах буферов, сменах брейкпойнтов
и циклах закрытия/открытия выдвижных панелей. Хост устанавливает
data-wippy-visible="true" | "false" перед подключением прямого пользовательского элемента
и обновляет его на месте при смене логического владения. Это не CSS,
не viewport и не видимость документа, и это никогда не подразумевает перемонтирование.
Vue-компоненты читают состояние через useHostVisibility() либо совмещают обычную
первоначальную загрузку с обновлениями при показе через useHostVisibilityRefresh(task).
Последняя выполняется после монтирования и затем только при точном false -> true. Не используйте
топик прокси @visibility в прямом WC; это канал сообщений iframe/Web Fragment.
Закрепляйте точный тег CDN — не ниже https://web-host.wippy.ai/webcomponents-1.0.52 — пока метка Draft 1 не снята.
Включение управляемой раскладки
Включите точку входа managed в конфигурации фасада и предоставьте объявление host_config.layout на бэкенде:
host_config:
layout:
layouts:
default:
direction: horizontal
children:
- panel: nav
size: 240px
- panel: main
size: 1fr
main: true
panels:
nav: { kind: builtin, id: '@HOST/nav-sidebar' }
main: { kind: page, id: home }
Когда выбрана точка входа managed, фасад раздаёт managed-layout.js вместо module.js. fe_mode — текущий параметр требования фасада (по умолчанию compat, опционально managed); он задаётся в требовании wippy.facade, а не переносится внутри полезной нагрузки AppConfig. Поля AppConfig.feature не существует — управляемая раскладка передаётся потомку целиком через AppConfig.hostConfig.layout. Поверхность proxy API идентична в обоих режимах, но некоторые команды действуют только в одном из них — см. Что работает в каком режиме.
HostLayoutDeclaration
Вся раскладка описывается одним объектом HostLayoutDeclaration, вложенным в host_config.layout на бэкенде в конфигурации фасада и проецируемым во фронтенд как AppConfig.hostConfig.layout. Хост валидирует его до монтирования — любая LayoutValidationError выводится в консоль браузера в виде { kind, message, panelId? }.
| Поле | Тип | Описание |
|---|---|---|
layouts |
Record<string, PanelTree> & { default: PanelTree } |
Деревья панелей по ключам брейкпойнтов. Ключ default обязателен. |
breakpoints? |
Record<string, number> |
Пиксельные ширины, активирующие ключи раскладок, отличные от default. |
panels |
Record<string, HostPanelDef> |
Именованные определения содержимого панелей. |
floating? |
Record<string, HostFloatingDef> |
Плавающие панели-оверлеи на момент запуска. |
modals? |
Record<string, HostModalDef> |
Определения модальных окон на момент запуска. |
coordinators? |
Record<string, HostCoordinatorDef> |
Headless-компоненты-координаторы. |
services? |
Record<string, HostCoordinatorDef> |
Устаревший псевдоним для coordinators; новые объявления должны использовать coordinators. |
dragEnabled? |
boolean | Разрешить перетаскивание разделителей пользователем. По умолчанию true. |
Виды панелей
Каждая запись в panels, floating, modals и coordinators — размеченное объединение по kind:
| Вид | Описание | Обязательные поля |
|---|---|---|
page |
Модуль страницы Wippy, смонтированный в srcdoc-iframe | id (id страницы в реестре) |
artifact |
Артефакт Wippy, смонтированный в srcdoc-iframe | id (UUID артефакта) |
component |
Веб-компонент, смонтированный прямо в DOM хоста | tagName |
builtin |
Компонент хоста, принадлежащий фреймворку (см. ниже) | id |
Ровно одна панель в дереве раскладки должна нести main: true. Владение URL браузера по-прежнему требует синхронизации маршрутов через @HOST/compat-coordinator или эквивалентную координацию на стороне потребителя. Все остальные панели маршрутизируются независимо внутри своих iframe.
Встроенные идентификаторы панелей
kind: builtin принимает следующие значения id. Префикс @HOST/ зарезервирован за панелями, принадлежащими фреймворку:
| ID | Что отрисовывает |
|---|---|
@HOST/nav-sidebar |
Стандартную боковую панель навигации Wippy (сессии, страницы, настройки) |
@HOST/chat-wrapper |
Стандартную панель чата Wippy для активной сессии |
@HOST/artifact-viewer |
Универсальный просмотрщик артефактов (сочетайте с маршрутом /:uuid) |
@HOST/session-selector |
Список и выбор сессий |
@HOST/compat-coordinator |
Headless-координатор compat-намерений и главного маршрута; объявляйте в coordinators |
@HOST/panel-tab |
Краевую вкладку для раскрытия свёрнутой панели; объявляйте в floating |
Неизвестный @HOST/<id> вызывает LayoutValidationError при загрузке объявления, а не молча отрисовывает пустой слот.
Раскладки по ключам брейкпойнтов
Поле layouts сопоставляет ключи брейкпойнтов с деревьями панелей. default используется всегда, если не совпал более узкий брейкпойнт. Пиксельные ширины брейкпойнтов определяются в breakpoints:
host_config:
layout:
breakpoints:
sm: 768
layouts:
default:
direction: horizontal
children:
- panel: side
size: 300px
- panel: main
size: 1fr
main: true
sm:
direction: vertical
children:
- panel: main
size: 1fr
main: true
- panel: side
display: drawer-left
drawerSize: { width: 320px }
panels:
side: { kind: page, id: app-sidebar, route: / }
main: { kind: page, id: app-home, route: / }
При смене брейкпойнта панели с одним и тем же id сохраняют один стабильный хост содержимого, визуально следующий за активным слотом без смены родителя. contentWindow iframe, состояние веб-компонента, состояние Vue и позиция прокрутки переживают переход; смена родителя через Teleport намеренно не используется, поскольку удаление и повторная вставка iframe перезагружают его.
Панели в режиме выдвижной панели
Слот панели может объявить display: 'drawer-left' | 'drawer-right' | 'drawer-bottom', чтобы отрисовываться как выезжающий оверлей вместо встроенного flex-элемента. Выдвижные панели:
- Не участвуют в определении размеров дорожек своего родительского контейнера (
sizeигнорируется) - Отрисовываются как абсолютно позиционированные оверлеи, привязанные к названному краю
- Имеют состояние открыто/закрыто, переключаемое через
host.layout.openDrawer(id)/closeDrawer(id)/toggleDrawer(id) - Показывают подложку в открытом состоянии; щелчок по подложке закрывает все открытые выдвижные панели
Слоты с main: true не могут быть в режиме выдвижной панели — валидация хоста выбрасывает ошибку. Поле drawerSize.width управляет шириной для левых/правых выдвижных панелей; drawerSize.height — для нижних. По умолчанию 320px.
Плавающие панели
Плавающие панели — свободно позиционируемые оверлеи, объявляемые в floating. Они не участвуют в flex-дереве раскладки и могут добавляться и удаляться во время выполнения:
floating:
flap:
kind: component
tagName: my-right-flap
position: { x: 0, y: 200 }
size: { width: 48, height: 80 }
Управление во время выполнения:
// Добавить плавающую панель
host.layout.addFloating('inspector', {
kind: 'component',
tagName: 'my-inspector',
position: { x: 100, y: 100 },
size: { width: 400, height: 300 },
})
// Удалить её
host.layout.removeFloating('inspector')
Headless-координаторы
Координаторы — компоненты, монтируемые в скрытом хосте. У них нет видимого слота, но они получают proxy API, ограниченный панелью. Используйте их для сквозной логики, чтобы отображающие панели оставались сосредоточены на отрисовке. Более старое поле services остаётся устаревшим псевдонимом для совместимости.
coordinators:
coordinator:
kind: component
tagName: my-coordinator
Компонент-координатор получает обёртку хоста, ограниченную панелью, и может подписываться на каналы шины сразу в onMount:
import { WippyElement } from '@wippy-fe/webcomponent-core'
class MyCoordinator extends WippyElement {
protected onMount() {
this.host?.layout.on('open-chat', ({ payload }) => {
this.host?.layout.updatePanel('right', { route: `/open-chat/${payload.token}` })
this.host?.layout.expandPanel('right')
})
}
protected onUnmount() {}
static get wippyConfig() { return { propsSchema: { properties: {} } } }
}
customElements.define('my-coordinator', MyCoordinator)
Поставляемый compat-координатор
Управляемая раскладка содержит только объявленные поверхности. Поэтому вызовы вроде
host.openArtifact(), host.startChat(), host.openSession() и
host.navigate() публикуют типизированные намерения в зарезервированном
канале @HOST/intent. Объявите поставляемый координатор, чтобы он их обрабатывал и
привязывал URL браузера к главной панели:
coordinators:
compat:
kind: builtin
id: '@HOST/compat-coordinator'
props:
artifactPanel: right
chatPanel: chat
modalId: artifact-modal
routeSync: true
wsActions: true
Сохраняйте routeSync: true при использовании стандартного контракта навигации. Без
координатора или эквивалентной логики на стороне потребителя у глубоких ссылок, кнопок «Назад»/«Вперёд» и
навигации @HOST/nav-sidebar нет маршрута панели, которым можно управлять. Намерения, поднятые
во время загрузки потомка, удерживаются в ограниченной очереди до подписки первого координатора.
@HOST/ зарезервирован в обе стороны: обычные панели не могут публиковать системный
трафик, и только записи в coordinators получают его через поддерживаемые
API хоста. Эта граница обеспечивается для панелей iframe/Web Fragment. Прямой
компонент, смонтированный в области хоста, разделяет DOM хоста и не является песочницей
безопасности. При запуске хост печатает таблицу соответствия, когда отсутствуют обработка координатора,
целевая поверхность модального окна, привязка URL к главной панели или объявленный тег координатора;
полное объявление не порождает предупреждений.
Внутривкладочная шина broadcast
Панели общаются через шину, ограниченную текущей вкладкой браузера. Шина никогда не выходит за пределы вкладки — если нужна синхронизация между вкладками, используйте собственный топик WebSocket.
| Метод | Описание |
|---|---|
host.layout.broadcast(channel, payload) |
Публикация всем панелям; отправитель исключён |
host.layout.send(targetPanelId, channel, payload) |
Публикация одной конкретной панели |
host.layout.on(channel, handler) |
Подписка; возвращает функцию отписки off() |
Поле sourcePanelId в полученных сообщениях устанавливается хостом по публикующему окну и не может быть подделано. Имена каналов — обычные строки, чувствительные к регистру.
Важно: компоненты, импортирующие host напрямую из @wippy-fe/proxy, обходят ограничение областью панели — вызовы шины проходят, но теряют sourcePanelId. Всегда используйте обёртку, ограниченную панелью:
// обычный HTMLElement
import { getWippyHost } from '@wippy-fe/webcomponent-core'
const host = getWippyHost(this)
// подкласс WippyElement — this.host уже ограничен панелью
this.host?.layout.broadcast('open-chat', { token: 'abc' })
// Vue-компонент
import { useHost } from '@wippy-fe/webcomponent-vue'
// ProxyApiInstance — ambient-глобальный тип (из @wippy-fe/types-global-proxy) — ссылайтесь на него без импорта.
const host = useHost<ProxyApiInstance['host']>()
host?.layout.broadcast('open-chat', { token: 'abc' })
Справочник API раскладки (host.layout)
| Метод | Описание |
|---|---|
.snapshot |
Синхронный геттер, возвращающий полный снимок раскладки или null вне режима управляемой раскладки |
.resizePanel(id, size) |
Изменить размер названной панели в активном брейкпойнте |
.collapsePanel(id) |
Свернуть панель, объявленную с collapsible: true |
.expandPanel(id) |
Развернуть свёрнутую панель |
.openDrawer(id) |
Открыть панель в режиме выдвижной панели |
.closeDrawer(id) |
Закрыть панель в режиме выдвижной панели |
.toggleDrawer(id) |
Переключить панель в режиме выдвижной панели |
.movePanel(id, target) |
Переместить панель в новую позицию дерева |
.removePanel(id) |
Удалить панель из раскладок всех брейкпойнтов |
.updatePanel(id, def) |
Пропатчить определение панели во время выполнения; props сливается поверхностно, поля верхнего уровня заменяются |
.addFloating(id, def) |
Добавить плавающую панель |
.removeFloating(id) |
Удалить плавающую панель |
.openModal(id, def?) |
Открыть объявленное модальное окно по id, при необходимости переопределив его определение. Модальным окнам, создаваемым во время выполнения, требуется def. По умолчанию используется нативный <dialog>.showModal(); передайте useNativeDialog: false для устаревшего div-оверлея. Повторное открытие уже открытого id — молчаливый no-op. |
.closeModal(id) |
Закрыть открытое модальное окно |
.broadcast(channel, payload) |
Публикация всем панелям |
.send(target, channel, payload) |
Публикация одной панели |
.on(channel, handler) |
Подписка на канал шины |
openModal() документирует внутреннюю инфраструктуру раскладки хоста, а не рецепт для компонента приложения. Поставляемый продуктовый UI на Vue должен использовать Dialog из PrimeVue или API подтверждения хоста, а не клонировать это поведение нативного диалога с собственной стилизацией модальных окон.
Семантика слияния updatePanel
host.layout.updatePanel(id, def) патчит существующее определение панели, а не заменяет его. Объект props сливается поверхностно с текущими props панели: переданные ключи добавляются или перезаписываются, опущенные сохраняются. Каждое другое поле верхнего уровня в def (route, kind, id, tagName, title, icon, …) заменяет текущее значение целиком.
Для панели, текущие props которой равны { artifactId: 'old', zoom: 2 }:
// props сливается поверхностно → { artifactId: 'abc', zoom: 2 }
host.layout.updatePanel('right', { props: { artifactId: 'abc' } })
// route заменяется целиком; props остаются нетронутыми
host.layout.updatePanel('right', { route: '/x' })
Две оговорки: слияние props поверхностное — вложенный объект внутри props заменяется целиком, а не сливается глубоко, — и поверхностное слияние не может удалить ключ prop (его можно только перезаписать).
Composable-функции Vue — @wippy-fe/vue-host
Эти composable-функции оборачивают proxy-API раскладки в реактивные ref Vue 3. Нижележащая подписка имеет модульную область и живёт весь срок жизни iframe, поэтому очистки на уровне компонента при демонтировании нет:
| Composable | Возвращает |
|---|---|
useWippyLayout() |
Полное состояние раскладки и методы мутаций |
useWippyPanel(panelId) |
Живое состояние названной панели (panelId обязателен — string, Ref<string> или геттер) |
useWippyBreakpoint() |
Имя активного брейкпойнта как реактивный ref |
useWippyMainRoute() |
Реактивный ref на текущий маршрут главной панели |
Эти composable-функции никогда не возвращают null — они всегда отдают объекты/ref, чьё внутреннее .value деградирует при отсутствии хоста с управляемой раскладкой: useWippyLayout().snapshot.value равен null (а isManaged.value равен false, поэтому мутации становятся молчаливыми no-op), useWippyBreakpoint().value и useWippyMainRoute().value — пустые строки, а useWippyPanel(id).value равен null, когда такого id нет. Проверяйте наличие хоста через layout.isManaged.value (или layout.snapshot.value !== null), а не проверкой === null на возвращаемом значении. Это позволяет использовать composable-функции в автономных песочницах и модульных тестах, где хоста с управляемой раскладкой нет.
Буферизация подмен без перемонтирования
useSwapBuffer() из @wippy-fe/layout держит уходящую поверхность смонтированной,
пока входящее содержимое не сообщит о готовности, с явным потолком тайм-аута.
Используйте неизменяемый slot.index как ключ DOM, передавайте и индекс, и ключ содержимого в
markReady() / markFailed(), чтобы устаревшие асинхронные сигналы отклонялись, и держите
ошибки ограниченными своим буфером. Идентичность содержимого принадлежит keyOf; изменение ключа
DOM привело бы к повторной вставке iframe и уничтожило бы состояние, которое буферизация призвана сохранить.
const swap = useSwapBuffer<Surface>({
keyOf: surface => surface.ownerId,
buffers: 2,
readyTimeoutMs: 8_000,
loaderDelayMs: 250,
loaderMinMs: 400,
})
const slot = swap.push(surface)
swap.markReady(slot.index, slot.key)
// или: swap.markFailed(slot.index, error, slot.key)
Показанные значения — значения по умолчанию. Тайм-аут готовности по умолчанию показывает содержимое,
а не оставляет устаревшее содержимое под индикатором загрузки. Привязывайте UI загрузки к
swap.showLoader, а не напрямую к готовности. Отказавший буфер остаётся изолированным
от соседнего; после обработки ошибки вызовите clearError(index) для повторной попытки.
Готовность страниц в Web Host
Web Host использует ту же дисциплину готовности по ключу для управляемых поверхностей страниц,
с финальным потолком показа в 14 секунд. Рендереры iframe и прямых веб-компонентов
испускают load / error через слушатели событий Vue и включают неизменяемый
ключ содержимого, принадлежащий этому рендереру. Отрисованное содержимое поэтому показывается
немедленно; потолок — лишь запасной вариант для содержимого, которое никогда не сообщает о готовности.
Позднее событие от вытесненного рендерера отклоняется, если индекс его буфера
уже переиспользован.
Не используйте 14-секундный потолок хоста как задержку загрузки приложения и не добавляйте второй таймер вокруг обычной готовности страницы. Страница, регулярно достигающая потолка, имеет сломанный путь готовности или жизненного цикла, который следует исправить у её владельца.
Стабильные обновления компонентов и размеры панелей
Для kind: component изменение props панели обновляет или удаляет атрибуты
существующего пользовательского элемента. Хост заменяет элемент только при изменении tagName.
Это сохраняет состояние, принадлежащее элементу, при вызовах updatePanel() и
переходах между брейкпойнтами.
minSize и maxSize ограничивают только активную ось разделения: ширину в
горизонтальном дереве и высоту в вертикальном. Они не ограничивают поперечную ось,
поэтому навигация, чат и другие монтирования во всю высоту могут заполнять свою дорожку. Выдвижные
монтирования следуют анимированной геометрии выдвижной панели и поднимаются над своим якорем
и подложкой только пока открыты, без перемонтирования содержимого.
Стилизация разделителя и рукоятки
Область попадания разделителя шире его видимой линии и находится в изолированном
стеке слоёв пакета. --wippy-layout-splitter-z-index по умолчанию равен 700, ниже
выдвижных панелей и подложек модальных окон. Круглая рукоятка подключается по желанию:
| Переменная | По умолчанию | Назначение |
|---|---|---|
--wippy-layout-splitter-size |
1px |
Толщина видимой линии разделителя |
--wippy-layout-splitter-hit-size |
10px |
Область попадания указателя вокруг линии; 24px для грубых указателей |
--wippy-layout-splitter-z-index |
700 |
Слой разделителя и рукоятки |
--wippy-layout-splitter-handle-size |
0 |
Диаметр рукоятки; 0 отключает её |
--wippy-layout-splitter-handle-bg |
transparent |
Заливка рукоятки |
--wippy-layout-splitter-handle-border |
0 solid transparent |
Сокращённая запись границы |
--wippy-layout-splitter-handle-shadow |
none |
Тень рукоятки |
--wippy-layout-splitter-handle-icon-color |
transparent |
Цвет SVG с учётом темы через currentColor |
Подключая рукоятку, задавайте размер, заливку, границу/тень и цвет иконки вместе. SVG поворачивается на 90 градусов для вертикальных разделителей и остаётся скрытым для заблокированных разделений.
Что работает в каком режиме
Поверхность proxy API идентична в режимах compat и managed — одни и те же импорты @wippy-fe/proxy разрешаются в обоих, — но две её части зависят от режима по эффекту. Это несоответствие — главное, за чем нужно следить при переносе приложения на управляемую раскладку (и одна из причин, почему managed всё ещё в раннем доступе).
host.layout действует только в режиме managed
Хост устанавливает приёмник раскладки только когда раскладка объявлена (точка входа managed, обусловленная hostConfig.layout). В режиме compat host.layout всё равно существует, но host.layout.snapshot равен null, а каждая мутация и вызов шины (resizePanel, updatePanel, movePanel, openModal, addFloating, broadcast, send, on, …) — молчаливый no-op: сообщение отправляется, но на стороне хоста никто не слушает. Проверяйте снимок перед мутацией:
if (host.layout.snapshot) {
host.layout.updatePanel('right', { route: '/details' }) // только managed
}
// Vue: const { isManaged } = useWippyLayout(); if (isManaged.value) { … }
(Отдельно — по другой оси — addPanel и setLayout вообще не предоставляются через прокси ни в одном из режимов; см. Известные ограничения.)
Команды host.*, предполагающие оболочку compat
Управляемая оболочка отрисовывает только вашу объявленную раскладку. Начиная с Web Host 1.0.50 команды, обычно нацеленные на обрамление compat, вместо молчаливого отказа публикуют типизированные сообщения @HOST/intent. Объявите @HOST/compat-coordinator или реализуйте эквивалентный координатор, чтобы отобразить эти намерения на ваши панели:
Команда host.* |
Compat (по умолчанию) | Managed |
|---|---|---|
setContext, toast, confirm, handleError, logout, bridge.*, state / ws / on верхнего уровня |
Работает | Работает напрямую; managed монтирует глобальные поверхности уведомлений и подтверждений |
openArtifact(id, ...) |
Открывает в правой панели или модальном окне | Публикует намерение; compat-координатор направляет его в artifactPanel или modalId |
startChat(token) / openSession(uuid) |
Открывает и отображает сессию | Публикует намерение; compat-координатор разрешает стартовые токены и обновляет объявленный chatPanel |
navigate(url) |
Выполняет push в корневом маршрутизаторе compat | Публикует намерение; routeSync применяет его к главной панели и держит историю браузера согласованной |
onRouteChanged(route, navId?) |
Управляет URL браузера у хоста | Обновляет состояние маршрута панели; routeSync проецирует маршрут главной панели в URL браузера |
Если координатор ещё недоступен, намерения времени запуска удерживаются в ограниченной очереди до первой подписки координатора. Объявление без обработчика отмечается в таблице соответствия при запуске. Зарезервированные намерения читаются только записями coordinators и не могут быть подделаны обычными панелями.
Подход к управлению состоянием
Три уровня в порядке предпочтения:
Маршрут — если пользователь может осмысленно добавить состояние в закладки или поделиться им, поместите его в URL. Каждая панель kind: page запускает собственный маршрутизатор и реагирует на события @history. Это развязано, поддерживает глубокие ссылки и учитывает историю браузера.
Снимок раскладки — если это влияет на форму раскладки (размеры, признаки свёрнутости, props компонентов), поместите это в снимок через updatePanel или resizePanel. Каждая подписанная панель видит каждое изменение снимка, поэтому держите полезную нагрузку небольшой.
Локально в панели — всё остальное (черновики форм, состояние модальных окон, временный UI) остаётся внутри собственных хранилищ Pinia или ref панели и никогда её не покидает.
Канонический паттерн координации
Рекомендуемый паттерн межпанельного взаимодействия: событие шины → сервис-координатор → updatePanel → панель реагирует через собственный маршрутизатор.
// В сервисе-координаторе
this.host?.layout.on('open-chat', ({ payload }) => {
this.host?.layout.updatePanel('right', { route: `/open-chat/${payload.token}` })
this.host?.layout.expandPanel('right')
})
// В приложении правой панели (обычный модуль страницы на Vue)
const router = createAppRouter([...])
// createAppRouter уже отражает события истории хоста в маршрутизатор
// с защитой от эха и по текущему маршруту; не добавляйте ручную подписку на маршрутизацию.
Держите координаторы тонкими. Пусть панели владеют собственным UI.
Известные ограничения
По состоянию на Draft 1 следующее ещё не реализовано:
addPanel/setLayoutчерез прокси — не выпущены. Они существуют только у внутреннегоLayoutManagerиз@wippy-fe/layoutи не предоставляются через границу прокси iframe. (openModal,closeModalиmovePanelвыпущены — см. справочник API раскладки.)- UI перетаскивания панелей — модель данных и API
movePanel()работают; пользовательское перетаскивание ещё не реализовано. - Примитив вкладок — ещё не реализован.
- Контейнер-сетка плиток — запланирован на будущее.
- Сохранение мутаций времени выполнения — мутации не сохраняются между перезагрузками. При необходимости сохраняйте их вручную:
on('@layout-change', () => state.set('layout', host.layout.snapshot) ) - Точки расширения слота заголовка
nav-sidebar— позиции логотипа, имени приложения и кнопки переключения в этом черновике фиксированы.
См. также
- Точка входа фасада — как фасад загружает точку входа JS-модуля и доставляет конфигурацию
- Последовательность запуска — как хост при запуске переключается на точку входа управляемой раскладки
- Пакеты —
@wippy-fe/layout,@wippy-fe/vue-host,@wippy-fe/webcomponent-core,@wippy-fe/webcomponent-vue