AdaUI v0 — UI как данные для навыков и сценариев (без iframe)
0) цель и границы
AdaUI v0 даёт способ визуализации навыков/сценариев без встраивания удалённого кода (iframe/web-components).
Навык публикует два документа: view (описание интерфейса) и state (данные).
SPA-клиент локально рендерит UI по декларативной схеме и живёт на кэше при офлайне.
1) ключевые принципы
- декларативно: UI — это JSON (
view) + JSON-состояние (state) по фиксированной схеме «AdaUI v0». - локальный рендер: рендерер в браузере (общий компонент-кит), никаких удалённых скриптов навыков.
- двухуровневое хранилище: навыки пишут только в hub-store, клиент читает/пишет только в browser-store.
- реактивная синхронизация: hub-store ⇄ browser-store через Yjs (CRDT), офлайн-устойчивость «из коробки».
- ветвление документов:
main,owner/*,device/*,ab/*,preview/*; клиент собирает оверлей. - безопасность по ключам: ACL на документ, короткоживущие токены, навыки не видят device-ид.
- минимальный словарь компонентов: чтобы LLM легко генерировал корректные
view.json.
2) модель данных
2.1 ключ документа
doc_key = "{owner_id}/{skill_id}/{doc_type}:{branch}"
doc_type ∈ {view, state}
branch ∈ {"main","owner/{id}","device/{id}","ab/A","ab/B","preview/{token}"}
2.2 документ (минимальная форма)
{
"key": "owner1/weather_skill/state:main",
"rev": "l42-v3", // логический ревизионник (для индекса/аудита)
"etag": "sha256:...", // контроль содержимого snapshot (служебно)
"meta": {
"schema": "adaui/v0",
"ttl": "8h",
"acl": ["OWNER:read", "ROLE:SCENARIO:read", "SKILL:weather:write"]
},
"content": { /* произвольный JSON */ }
}
2.3 оверлей веток на клиенте (приоритет)
device/* → owner/* → ab/* → main (для каждого doc_type отдельно).
3) синхронизация и хранилища
3.1 выбор механизма
Yjs (CRDT) + y-websocket для транспорта.
Hub хранит Y-документы на диске и ведёт docs_index в SQLite для ACL/TTL/поиска.
3.2 hub-store
- персист:
y-leveldb/y-sqliteдля содержимого; - индекс: таблица SQLite
docs_index(key TEXT PK, acl TEXT, schema TEXT, ttl_sec INT, updated_at, etag TEXT); - задачи: проверка доступа, TTL-GC/снапшоты, аудит.
3.3 browser-store
- персист:
y-indexeddb(поdoc_key); - сервис
syncClient: подключение к/sync, подписки на комнаты (по ключам/префиксам), ACK, реконнект.
3.4 протокол (в общих чертах)
WS /sync(совместим с y-websocket).- «комната» =
doc_key; join допускается только после ACL-проверки. - изменения текут как CRDT-операции; офлайн-мердж — автоматический.
4) контракт навыка
4.1 manifest.json (фрагмент)
{
"id": "weather_skill",
"version": "2.1.0",
"capabilities": {
"visual": true,
"ui": {
"schema": "adaui/v0",
"title": "Погода",
"icon": "sun-cloud",
"modes": ["modal", "panel"],
"sizeHints": { "w": 480, "h": 360 }
},
"streams": {
"supportsDeclarativeInterest": true
}
},
"permissions": { "ownerOnly": false }
}
4.2 взаимодействие навыка с хранилищем
- навык пишет
view(обычно через сервис registry/деплой) и обновляетstateв hub-store: получает серверныйY.Docпо ключу и вносит изменения через Yjs API; - подписывается на свои ключи (если нужно реактивно читать/реагировать на команды).
5) AdaUI v0 — схема view
5.1 JSON-схема (сокращённая)
{
"schema": "adaui/v0",
"meta": { "title": "..." },
"layout": { "$ref": "#/$defs/node" },
"$defs": {
"node": {
"type": "object",
"required": ["type"],
"properties": {
"type": { "enum": ["title","text","badge","metric","stack","row","card","table","chart.line","button","select","empty"] },
"text": { "type": "string" },
"label": { "type": "string" },
"value": { "type": "string" },
"icon": { "type": "string" },
"gap": { "type": "number" },
"title": { "type": "string" },
"child": { "$ref": "#/$defs/node" },
"children": { "type": "array", "items": { "$ref": "#/$defs/node" } },
"columns": { "type":"array","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string"},"label":{"type":"string"}}} },
"rows": { "type": "string" }, // binding: "{{state.rows}}"
"series": { "type":"array","items":{"type":"object","properties":{
"label":{"type":"string"}, "x":{"type":"string"}, "y":{"type":"string"}, "data":{"type":"string"}
}}},
"bind": { "type": "string" }, // e.g. "{{state.level}}"
"options": { "type": "array", "items": { "type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"}} } },
"action": { "type":"object","properties":{
"type": { "enum": ["http.get","http.post","bus.publish","navigate"] },
"path": { "type": "string" },
"body": { "type": "object" },
"topic": { "type": "string" },
"payload": { "type": "object" },
"mode": { "enum": ["modal","panel"] }
}}
}
}
}
}
5.2 биндинги и действия
- биндинги: только
{{state.*}}и{{meta.*}}; массивы —[*]. -
действия:
-
http.get|post { path, body? }— уходит в command-relay (см. §7.3), не напрямую в навык; bus.publish { topic, payload }— публикация в шину;navigate { mode }— локальная навигация SPA.
6) SPA (frontend) и рендер
6.1 компоненты
StatusBar(hub online, latency, reconnect)DesktopScenario(контейнер рабочего стола)IconGrid(список визуальных навыков)ModalHost(диалог/панель; без iframe, с локальным рендером AdaUI v0)AdaUIRenderer(рантайм рендера поview+state)
6.2 состояние
authState: AUTH/PAIRED/ONLINEdesktopState: icons[], pinned[], order[] (читается из store)modalState: { open, skill_id, sizeHints } (контролы SPA)
6.3 офлайн-поведение
view/stateберутся из browser-store;http.post-действия попадают вops_outbox(очередь), реплеятся при онлайне;- «живые» обновления восстанавливаются после реконнекта по CRDT.
7) системные сервисы (hub)
7.1 sync-endpoint
WS /sync(y-websocket совместимо)- аутентификация токеном; сопоставление
doc_key → room; ACL-проверка на join
7.2 docs_index API (минимум)
GET /docs/index?prefix={owner_id}/...→ список ключей с метаданными (для discovery)- TTL-обслуживание (GC/снэпшоты) — фоновые задачи
7.3 command-relay
- SPA-действия
http.get|postне бьют по навыку напрямую; кладём команду вowner/{id}/commands/state:device/{device_id}или вызываем системный REST, а обработчик на hub/skill меняет соответствующийstate. - ответ пользователю приходит как изменение
stateпо текущему ключу — реактивно.
8) сценарий «Рабочий стол» и «Менеджер иконок»
8.1 ключи документов
owner/{id}/desktop/view:main
owner/{id}/desktop/state:main
owner/{id}/icon_manager/view:main
owner/{id}/icon_manager/state:main // порядок, пины, группы
8.2 API icon_manager (через store)
- список визуальных навыков формируется по реестру манифестов + ACL → записывается в
icon_manager/state. - пин/порядок меняются экшенами SPA → через command-relay корректируется
icon_manager/state.
9) пример навыка: weather_skill
9.1 ветки
owner/{id}/weather_skill/view:main
owner/{id}/weather_skill/state:main // агрегированный срез
owner/{id}/weather_skill/state:device/{device_id} // локальные предпочтения
9.2 пример view (сжато)
{
"schema":"adaui/v0",
"meta":{"title":"Погода"},
"layout":{"type":"stack","gap":12,"children":[
{"type":"title","text":"{{meta.title}}"},
{"type":"row","gap":12,"children":[
{"type":"metric","label":"сейчас","value":"{{state.now.temp_c}} °C","icon":"thermometer"},
{"type":"badge","text":"{{state.now.conditions}}","icon":"cloud"}
]},
{"type":"card","title":"прогноз 6 ч","child":{
"type":"chart.line",
"series":[{"label":"T, °C","x":"{{state.forecast[*].ts}}","y":"{{state.forecast[*].temp_c}}"}]
}},
{"type":"row","justify":"end","children":[
{"type":"button","text":"обновить","action":{"type":"http.post","path":"/v1/refresh"}}
]}
]}
}
9.3 декларативный интерес (дополнительно)
POST skills/weather_skill/v1/interest
{ "streams":[{ "topic":"weather.now", "area":{...}, "period":"5m", "ttl":"8h", "format":"compact" }] }
Навык сам обновляет state в hub-store; клиент ничего напрямую у навыка не запрашивает.
10) безопасность
- доступ к
doc_keyдаёт только hub после проверки ACL (OWNER, роли сценариев, skill-scopes). - навыки видят только
owner_idи свойskill_id; никаких device-ид. - все клиентские действия — через командные документы/шину, а не прямой HTTP в навык.
docs_indexподписываетview(etag, sig) — защита от подмены.
11) LLM-дружелюбность
- фиксированный словарь компонентов и ключей;
view.jsonи примерstate.jsonлегко генерируются;- патчи — малы и понятны (CRDT-операции или JSON Patch, если нужен экспорт);
- валидация по JSON Schema с понятными ошибками.
12) MVP-беклог (минимум)
hub
- [ ]
WS /sync(y-websocket совместимый) + ACL-обёртка - [ ]
docs_index(SQLite) + TTL-GC - [ ] command-relay (командный документ + обработчик)
browser
- [ ]
store/yjs: y-indexeddb, подключение к/sync, подписки по ключам/префиксам - [ ] overlay-резолвер веток
- [ ] очередь
ops_outbox(повтор экшенов после онлайна)
spa
- [ ]
AdaUIRenderer(компоненты v0: title, text, badge, metric, stack, row, card, table, chart.line, button, select, empty) - [ ] DesktopScenario, IconGrid, ModalHost (без iframe)
- [ ] офлайн-режим (render из кэша; экшены в очередь)
skills
- [ ] weather_skill:
view/stateв hub-store + обновлениеstateпо расписанию/интересу - [ ] icon_manager: формирование списка визуальных навыков и prefs в
state
tests
- [ ] авторизация → desktop с иконками
- [ ] клик по «Погоде» → модалка, график, метрика
- [ ] офлайн → UI из кэша; онлайн → дельты догоняются
- [ ] owner-only иконки недоступны не-owner
13) заметки по эволюции
- компоненты v0.1: tabs, form, chart.bar, progress
- двусторонние биндинги: локальный
ui.localдля форм и редактирования - подписки по префиксу:
owner/{id}/*/state:*для DesktopScenario - CRDT-экспорт в snapshots: периодическая компакция больших документов