Многопанельная раскладка

Статус: 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 — позиции логотипа, имени приложения и кнопки переключения в этом черновике фиксированы.

См. также