Maketa · документация

Документация Maketa

Maketa — конструктор экранов приложений и лендингов. Рисуете мышкой, делитесь ссылкой вместо ТЗ. А ещё — программно: ИИ и код читают и меняют дерево экрана, а экраны Flutter выгружаются в макет автоматически.

Эта страница — для тех, кто интегрируется с Maketa: разработчиков, ИИ-агентов и любопытных. Просто порисовать можно в редакторе без всякой документации.

Три способа управлять макетом программно:

  • MCP-сервер — Claude, ChatGPT, Cursor, VS Code или Cline читает дерево экрана, добавляет и переносит узлы, делает ветки и синхронизирует экраны из кода.
  • maketa_sync — Flutter-пакет выгружает экран из работающего приложения в макет структурой, а не картинкой.
  • REST API — прямые HTTP-запросы для своих сценариев.

Быстрый старт

  1. Откройте редактор и нарисуйте экран (или вставьте скриншот через Ctrl/⌘+V).
  2. Нажмите «🔗 Поделиться» — макет сохранится на сервере, появятся ссылки на просмотр и редактирование.
  3. Чтобы подключить ИИ или код — там же откройте «🔑 Ключи для ИИ и кода» и создайте проектный ключ.
  4. Дальше: MCP-сервер для ИИ или maketa_sync для Flutter.

Аккаунт не обязателен, но с ним макеты собираются в «Мои макеты» и не теряются. Клиенты demda.pro входят той же учётной записью — вкладка «Клиент demda» в окне входа.

Основные понятия

Доска (проект) и экраны

Доска — единица хранения (один макет). В ней несколько экранов. Модель сцены — наш собственный JSON maketa.board.v2, единый источник истины для редактора, ИИ и нативных клиентов. Экран в нём — дерево узлов, а не список фигур с координатами.

Документ несёт "v": 2. Макеты, сохранённые до перехода на дерево, приходят без этого поля и читаются по старой плоской модели maketa.board.v1 (objects: […]); она оставлена только ради них и здесь больше не описывается. Новый код всегда шлёт "v": 2.

Экраны «из кода» и «дизайн»

ТипЧто этоПравка руками
designОбычный экран, нарисованный в редакторе или ИИ.Да
codeВыгружен из кода (Flutter). Источник истины — код. Каждый пуш — новая версия («коммит из кода»).Нет (заблокирован)

Ветки

У code-экрана нельзя менять узлы руками — но можно создать ветку: редактируемую design-копию, связанную с оригиналом (как в GitHub). Навигация двусторонняя: с оригинала — к веткам, из ветки — «← Оригинал». Когда код обновляется, ветки от старой версии помечаются «⚠ база обновилась». Всё дерево видно по кнопке «🌳 Дерево».

Доступ к макету: ссылки, права, соавторы

У доски одна ссылка для людей — https://maketa.pro/app/?b=BOARD_ID. Что по ней можно, решают права доски, а не сама ссылка.

Три режима доступа по ссылке

РежимКто смотритКто правит
view — «Только просмотр» (по умолчанию)любой, у кого есть ссылка; прототип кликаетсявладелец, соавторы, держатель edit-токена
edit — «Может редактировать»любойлюбой, у кого есть ссылка
none — «Только приглашённые»владелец и соавторывладелец и соавторы

Переключается в «Поделиться» → «Доступ по ссылке» или запросом POST /board/link-access с телом {id, edit?, access}. Менять режим могут владелец (по сессии) и держатель edit-токена.

Четыре режима: edit — править по ссылке; comment — смотреть и оставлять замечания прямо на экранах, без регистрации (гость называет только имя); view — только смотреть; none — только приглашённые. Замечания живут отдельно от макета: GET/POST /board/comments, ответы /board/comments/reply, статусы и удаление — владельцу. Пин привязан к узлу дерева и переживает перекладку макета; координаты — доли [0,1] от рамки узла и экрана.

Соавторы по e-mail

До 50 адресов на доску. Соавтор входит своим аккаунтом, видит доску в «Моих макетах» и правит её при любом режиме ссылки; его правки объединяются с вашими, а не затирают их. GET /board/collab?id=&edit= — список, POST /board/collab с {id, edit?, action: "add" | "remove", email} — управление.

Анонимная доска

Доска, созданная без входа, по умолчанию открывается на просмотр; править её может только тот, у кого есть edit-токен из ссылки редактирования (?b=…&e=…). Попытка сохранить чужую доску по одному лишь id — 403. Это проверяется автотестом против прода при каждом выпуске.

Что даёт каждый токен

ТокенОткудаЧто можно
edit-токен доскиссылка «Может редактировать»правка, права, ключи, соавторы этой доски
view-токенстарые ссылки просмотратолько просмотр — оставлен для совместимости, новым ссылкам не нужен
mk_pk_… проектный ключкабинет владельцаheadless-правка одной доски: код, CI, MCP
mk_sb_… песочный ключPOST /agent/registerдоски песочницы агента, 14 дней
OAuth-токен MCPвход в коннекторевсе доски аккаунта, 1 час (refresh — 30 дней)

Перевыпуск ссылок

Edit-токен доски перевыпустить пока нельзя — он действует, пока существует доска, а «Удалить» в «Моих макетах» убирает макет только из вашего списка. Если ссылка редактирования попала не к тому человеку — откройте макет, сделайте копию (у новой доски свои токены) и дальше работайте в ней.

Проектные ключи

Проектный ключ (mk_pk_…) разрешает ИИ и коду пушить экраны в конкретную доску — без вашего пароля и сессии. Отдельно от ссылок просмотра/редактирования.

Создать в кабинете

В редакторе: «🔗 Поделиться» → «🔑 Ключи для ИИ и кода». Кнопка «Создать ключ» — ключ показывается один раз, скопируйте сразу. Там же список ключей и «Отозвать».

Храните ключ в секрете — не коммитьте в git. Передавайте через переменные окружения / --dart-define. Скомпрометированный ключ отзовите в кабинете.

Или через API

curl -X POST https://maketa.pro/maketa/api/project/key \
  -H 'content-type: application/json' \
  -d '{"id":"BOARD_ID","edit":"EDIT_TOKEN","label":"CI"}'
# → {"key":"mk_pk_..."}

MCP-сервер (для ИИ)

MCP даёт нейросети инструменты читать и менять объекты макета, создавать экраны, делать ветки и синхронизировать экраны из кода. Сервер удалённый — ставить нечего, один URL:

https://maketa.pro/maketa/api/mcp

Работает с Claude (Code, веб, десктоп), ChatGPT, Cursor, VS Code, Cline и любым клиентом с поддержкой удалённых MCP-серверов. Английская версия этого раздела — Maketa MCP Server (English).

1. Удалённый коннектор с OAuth 2.1 — рекомендуется

Клиент открывает вход Maketa: авторизуйтесь единым аккаунтом (Maketa или клиент demda) и подтвердите доступ. Дальше ИИ работает только с вашими досками — начните с maketa_list_boards. Ключи прописывать не нужно, доступ отзывается в аккаунте.

  • Authorization code + PKCE S256, публичный клиент, refresh-токен на 30 дней (токен доступа — 1 час).
  • Динамическая регистрация клиента (RFC 7591): POST /maketa/api/oauth/register — клиент регистрируется сам, как у коннектора Figma.
  • Метаданные для строгих клиентов: https://maketa.pro/.well-known/oauth-protected-resource и …/oauth-protected-resource/maketa/api/mcp (RFC 9728 §3.1), …/.well-known/oauth-authorization-server (RFC 8414).
  • Запрос без токена отвечает 401 с заголовком WWW-Authenticate: Bearer resource_metadata="…" — клиент находит вход сам.

Подключение в один клик

КлиентКак
Claude Codeclaude mcp add --transport http maketa https://maketa.pro/maketa/api/mcp
Claude — веб и десктопSettings → Connectors → Add custom connector → вставить URL
CursorДобавить в Cursor или Settings → Tools & Integrations → New MCP Server и URL вручную
VS CodeДобавить в VS Code (нужен Copilot Chat в агентном режиме)
ChatGPTSettings → Connectors → режим разработчика → Create → URL. Только на платных тарифах
Cline и остальные{"mcpServers":{"maketa":{"type":"streamableHttp","url":"https://maketa.pro/maketa/api/mcp"}}}

Те же кнопки с копированием — на главной.

Инструменты — 13

ИнструментЧто делает
maketa_list_boardsДоски, доступные этому аккаунту или ключу. Всегда первый вызов
maketa_list_screensЭкраны доски со сводкой: тип (code/design), блокировка, число узлов, слоты, ветки, версия. Поле model говорит, дерево это (v2) или старый плоский экран (v1)
maketa_get_screenОдин экран целиком, как он лежит на диске: slots и плоская карта nodes
maketa_get_treeДерево экрана вложенным видом по слотам — читать структуру так дешевле и понятнее, чем картой. Отдаёт ещё detached: узлы, ни к кому не привязанные
maketa_create_screenНовый design-экран модели v2. Сразу получает корневой блок в слоте body — его id приходит в bodyId, первые узлы кладите в него
maketa_add_nodeДобавить узел в дерево: node плюс parentId (внутрь блока) или slot (top/body/bottom/over) и необязательный index
maketa_update_nodeЧастичный патч узла: name, w, h, layout, look, content, action, hidden, repeat. layout, look и content сливаются по полям: {"content":{"text":"Новый"}} не сотрёт размер и цвет; "action": null снимает действие
maketa_move_nodeПеренести узел к другому родителю или в слот и сменить порядок среди детей. В v2 это и есть «переместить»: координат у узла нет
maketa_remove_nodeУдалить узел вместе со всем поддеревом; ссылки на удалённое в действиях и пунктах снимаются сами
maketa_branch_screenРедактируемая ветка экрана — единственный способ править code-экран
maketa_push_code_screenUpsert code-экрана из исходного кода по стабильному ключу; каждый пуш — новая версия
maketa_list_branchesВетки экрана
maketa_get_parentРодитель ветки — вернуться к оригиналу из кода

Только читают: list_boards, list_screens, get_screen, get_tree, list_branches, get_parent. Остальные пишут в доску. Инструментов *_element больше нет: узел не двигают в координаты — его переносят к другому родителю. Ошибки инструментов приходят как isError: true с текстом по-русски; имена инструментов и схемы аргументов — английские, описания в tools/list пока на русском.

2. Headless — без OAuth и браузера

Тот же URL принимает ключи напрямую: Authorization: Bearer mk_pk_… (проектный ключ — инструменты в рамках своей доски, boardId обязателен) или Bearer mk_sb_… (песочный ключ агента — в рамках досок песочницы). Ключ принимается и в заголовке x-maketa-key.

curl -s -X POST https://maketa.pro/maketa/api/mcp \
  -H "Authorization: Bearer mk_sb_…" -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

3. Локальный сервер (stdio) — для CI и своих сценариев

Zero-dependency файл на Node ≥18 — mcp/maketa-mcp.mjs, конфигурируется env: доска, edit-токен, проектный ключ. Доска задаётся заранее, поэтому инструмента maketa_list_boards в нём нет — остальные 12 те же, с теми же именами и схемами аргументов.

Вопросы по интеграции — support@maketa.pro. Код сервера открыт под MIT: github.com/demdas/maketa-mcp. Maketa есть и в официальном реестре MCP под именем pro.maketa/maketa — домен подтверждён DNS-записью.

{
  "mcpServers": {
    "maketa": {
      "command": "node",
      "args": ["/path/to/mcp/maketa-mcp.mjs"],
      "env": {
        "MAKETA_BOARD": "BOARD_ID",
        "MAKETA_EDIT":  "EDIT_TOKEN",
        "MAKETA_PKEY":  "mk_pk_..."
      }
    }
  }
}

Что стоит знать до первой записи

  • Code-экраны не правятся напрямую (источник — код): ИИ вызывает maketa_branch_screen и правит ветку-предложение.
  • Координат у узла нет. Место ребёнка задаёт родитель порядком в children и своей раскладкой; «подвинуть» — это maketa_move_node с новым parentId или index.
  • Кликабельным макет делает action — {t:"goto",screen,mode} у узла или link у пункта tabs. Без переходов макет — набор картинок.
  • Нижнее меню — один узел tabs, а не пять прямоугольников; кнопка — button, а не прямоугольник с текстом; поле ввода — input; группа переключателей — один choice.
  • Десять одинаковых строк — это repeat: 10 на одном узле, а не десять копий.
  • Целостность дерева — отказ, а не тихая починка. Висячая ссылка, узел у двух родителей, цикл и глубина больше 12 возвращают ошибку с указанием узла: unknown_node, node_two_parents, tree_cycle, too_deep.
  • Транспорт — Streamable HTTP без SSE: только POST, ответ application/json, сессий нет. Версию протокола сервер возвращает ту, что прислал клиент в initialize (по умолчанию 2024-11-05).
  • Лимит — 600 запросов в час на ключ или пользователя.

Автономные агенты

Автономный ИИ-агент проходит путь «регистрация → доска → экран → ссылка → повторное чтение» без человека. «Без регистрации» на лендинге относится к браузеру; агенту нужен песочный ключ — он выдаётся мгновенно, без почты и оплаты.

Полная машиночитаемая схема всех ручек и ошибок — /openapi.json (OpenAPI 3.1).

Путь агента (curl)

# 1. Регистрация (лимит 3/сутки с IP; ключ показывается один раз, живёт 14 дней)
curl -s -X POST https://maketa.pro/maketa/api/agent/register \
  -H 'content-type: application/json' \
  -d '{"name":"my-agent","purpose":"макет для клиента"}'
# → {"key":"mk_sb_…","expiresAt":…,"quotas":{"boards":5,"savesPerDay":200},…}

# 2. Создать доску (доска привязывается к ключу). Пустой экран v2 — это слот body
#    с корневым блоком и карта nodes, а не objects: [].
curl -s -X POST https://maketa.pro/maketa/api/create \
  -H 'content-type: application/json' -H 'x-maketa-key: mk_sb_…' \
  -d '{"doc":{"v":2,"name":"Демо","screens":[{"id":"s_1","name":"Главная","device":"iphone-15","w":393,"h":852,"bg":"#FFFFFF","slots":{"body":"n_body"},"nodes":{
    "n_body":{"id":"n_body","type":"stack","name":"Содержимое","w":"fill","h":"fill","layout":{"dir":"down","pad":16,"gap":12}}}}]}}'
# → {"id":"BOARD_ID","edit":"…","view":"…"}

# 3. Наполнить экран деревом (edit-токен не нужен — своя доска по ключу).
#    Порядок и место задают children и layout родителя; пять одинаковых
#    карточек — это repeat:5, а не пять копий узла.
curl -s -X POST https://maketa.pro/maketa/api/save \
  -H 'content-type: application/json' -H 'x-maketa-key: mk_sb_…' \
  -d '{"id":"BOARD_ID","baseRev":1,"doc":{"v":2,"name":"Демо","screens":[{"id":"s_1","name":"Главная","device":"iphone-15","w":393,"h":852,"bg":"#FFFFFF",
   "slots":{"top":"n_hdr","body":"n_body","bottom":"n_nav"},"nodes":{
    "n_hdr":{"id":"n_hdr","type":"stack","w":"fill","h":56,"look":{"bg":"#FFFFFF"},"layout":{"dir":"right","pad":[0,16,0,16],"gap":8,"cross":"center"},"children":["n_ttl"]},
    "n_ttl":{"id":"n_ttl","type":"text","content":{"text":"Меню","size":22,"weight":700}},
    "n_body":{"id":"n_body","type":"stack","w":"fill","h":"fill","layout":{"dir":"down","pad":16,"gap":12,"scroll":true},"children":["n_card"]},
    "n_card":{"id":"n_card","type":"stack","w":"fill","repeat":5,"look":{"bg":"#F7F8FA","radius":14},"layout":{"dir":"down","pad":12,"gap":4},"children":["n_name"]},
    "n_name":{"id":"n_name","type":"text","content":{"text":"Пицца «Маргарита»","size":16,"weight":600}},
    "n_nav":{"id":"n_nav","type":"tabs","w":"fill","h":82,"content":{"variant":"bottom","active":0,"items":[{"text":"Меню","icon":"🍕"},{"text":"Корзина","icon":"🛒"}]}}}}]}}'

# 4. Ссылка для людей: https://maketa.pro/app/?b=BOARD_ID
# 5. Чтение: curl -s 'https://maketa.pro/maketa/api/load?id=BOARD_ID'
# Свои доски: GET /agent/boards; сверка ревизии: GET /rev?id=BOARD_ID

Квоты и ошибки

СитуацияОтвет
6-я доска на ключ403 {"error":"quota_boards","hint":…}
201-е сохранение за сутки429 {"error":"quota_saves","hint":…}
Просроченный ключ401 {"error":"key_expired","hint":…} — регистрируйте новый
Неизвестный/отозванный ключ401 {"error":"bad_key","hint":…}
Удалённая доска410 {"error":"gone","deletedAt":…,"reason":…}

Каждая ошибка API несёт машинный код error и русскую подсказку hint — агент понимает, что делать дальше, без чтения документации.

Политика времени жизни

  • Песочный ключ — 14 дней; квоты: 5 досок, 200 сохранений/сутки. ИИ-функции (/ai/*) и оплата песочному ключу недоступны.
  • Доски песочницы живут, пока жив ключ, плюс 30 дней; затем удаляются (навсегда, ответ 410). Доска, которую забрал живой аккаунт, становится обычной и не удаляется.
  • Повышение до постоянного доступа — проектный ключ mk_pk_: его выдаёт владелец доски в кабинете, он не истекает, пока не отозван.
  • Доски по заданиям студии demda.pro при миграциях сохраняются.

Flutter · maketa_sync

Dev-пакет maketa_sync обходит render-дерево текущего экрана Flutter и пушит его в Maketa как code-экран — каждый виджет отдельным объектом (не скриншот).

Подключение

# pubspec.yaml
dependencies:
  maketa_sync:
    path: ../path/to/maketa/sync/dart   # репозиторий github.com/demdas/maketa
final _sync = MaketaSync(
  apiBase: 'https://maketa.pro/maketa/api',
  boardId: const String.fromEnvironment('MAKETA_BOARD'),
  projectKey: const String.fromEnvironment('MAKETA_PKEY'),
);

// на корне экрана:
KeyedSubtree(key: _sync.rootKey, child: MyScreen());

// пуш (dev-кнопка / хоткей / хук на hot-reload):
await _sync.capture(screenKey: '/search', name: 'Поиск', route: '/search');
flutter run \
  --dart-define=MAKETA_BOARD=BOARD_ID \
  --dart-define=MAKETA_PKEY=mk_pk_...

Таблица соответствий

Flutter render-узелОбъект Maketa
RenderParagraphtext (строка, размер, цвет, вес)
RenderDecoratedBox + BoxDecorationrect / ellipse (заливка, скругление, обводка)
Material / Card / PhysicalShaperect (цвет, скругление)
RenderImagerect-плейсхолдер

Работает на Android и iOS. Для Flutter Web (dart:io недоступен) — см. Веб-приложения.

Оба экстрактора (Flutter и веб) пока отдают экран в старом плоском формате — /screen/push и maketa_push_code_screen принимают и его, и дерево v2 (screen.slots + screen.nodes); формат различается по наличию карты узлов. Перевод экстракторов на дерево — отдельный этап.

Веб-приложения

Для веба Flutter не нужен — большинство сайтов нативные (React, Vue, обычный DOM). maketa-sync-web — небольшой JS без сборки: обходит DOM и getComputedStyle, превращает элементы страницы в экран и пушит тем же /screen/push по проектному ключу (CORS разрешён).

<script src="https://maketa.pro/maketa-sync-web.js"></script>
<script>
  MaketaSyncWeb.push({
    boardId: 'BOARD_ID', projectKey: 'mk_pk_...',
    screenKey: location.pathname, name: document.title
  });
</script>

Или букмарклетом — на любом своём экране одним кликом. Круглые элементы → ellipse, текст → text, картинки → плейсхолдер. Подробнее — sync/web/README.md в репозитории demdas/maketa.

REST API

База: https://maketa.pro/maketa/api. Ответы — JSON. Доступ к доске — по токенам edit/view (из ссылки «Поделиться») или проектному ключу (заголовок x-maketa-key).

Доски

МетодПутьНазначение
POST/createСоздать доску → {id, edit, view}
POST/save{id, edit, doc, baseRev?} — сохранить сцену целиком
POST/save/patch{id, edit, baseRev, opId?, ops[]} — сохранить дельту: операции {op: set|del, path, value?} над узлами (screens/<sid>/nodes/<nid>), метаданными экрана (screens/<sid>), сквозными блоками (shared/slots, shared/nodes/<nid>), именем (name) и порядком экранов (screensOrder). Только v2; baseRev обязателен, 409 приходит с текущим doc; повтор с тем же opId не применяется дважды. ≤ 2000 операций, тело ≤ 1 МиБ
GET/load?id=&edit=|view=Загрузить сцену
GET/rev?id=Лёгкая сверка ревизии → {rev, updatedAt}
POST/board/link-access{id, edit?, access: edit|comment|view|none} — режим доступа по ссылке
GET/board/comments?id&edit? — замечания к макету: треды с пинами, canComment, canManage
POST/board/comments{id, edit?, screenId, anchor?|floating, text, name, t0} — новое замечание (гость — по ссылке с правом comment); /reply, /status, /delete, /settings, /ban — ответ, статус, удаление, закрытие приёма, блокировка гостя
GET POST/board/collabСоавторы по e-mail: список / {id, edit?, action, email}
POST/board/flex-used{id, edit?, scope?} — засчитать применение «Сделать гибким» (счётчик на макет живёт на сервере) → {used, max}
GET/plansКаталог тарифов: цены за месяц и год, пределы каждого тарифа, enabled. Без авторизации

Тарифы и ответ 402 plan_limit. Когда тарифы включены (GET /plans → enabled:true), у бесплатного тарифа есть пределы: 3 макета, 10 экранов и 2 МБ на макет, 1 соавтор-редактор, 3 применения «Сделать гибким»; проектные ключи и запись через удалённый MCP — в «Про». Просмотр по ссылке, чтение через API, экспорт экрана и заявка в студию не ограничиваются никогда. При упоре любая ручка записи (/create, /save, /save/patch, /screen/push, /board/collab, /project/key, инструменты MCP) отвечает 402 {error:"plan_limit", limit, used, max, plan, upgrade, hint}, где limit — boards | screens | bytes | collaborators | flex | mcpKeys | mcpWrite, а hint — подсказка по-русски. Проверяется только рост: макет, уже превысивший предел, сохраняется в прежнем размере. После окончания оплаты макеты сверх пределов «Бесплатно» открываются в /load с access:"view" и полем readOnly — ничего не удаляется, апгрейд открывает правку сразу. В MCP предел приходит как JSON-RPC error {code:402, data:{limit, upgrade, hint}}. Песочный ключ mk_sb_ работает на любом тарифе в своих квотах.

Автономные агенты

МетодПутьНазначение
POST/agent/registerПесочный ключ mk_sb_… на 14 дней (см. Автономные агенты)
GET/agent/boardsДоски ключа со ссылками. Заголовок x-maketa-key

Полная схема запросов, ответов и ошибок ({error, hint}, 410 для удалённого) — /openapi.json.

Экраны из кода и ветки

МетодПутьНазначение
POST/screen/pushUpsert code-экрана по key. Заголовок x-maketa-key
POST/screen/branch{id, edit, screenId} — создать ветку
curl -X POST https://maketa.pro/maketa/api/screen/push \
  -H 'content-type: application/json' \
  -H 'x-maketa-key: mk_pk_...' \
  -d '{"id":"BOARD_ID","key":"/search",
       "screen":{"name":"Поиск","w":393,"h":852,"bg":"#F4F4EF","slots":{"body":"n_root"},"nodes":{...}},
       "meta":{"route":"/search","commit":"abc","method":"widget-tree"}}'
# → {"ok":true,"screenId":"s_...","version":1}

Проектные ключи

МетодПутьНазначение
POST/project/key{id, edit, label} → ключ (один раз)
GET/project/keys?id=&edit=Список ключей (без секретов)
POST/project/key/revoke{id, edit, keyId} — отозвать (keyId — поле id из списка ключей)

Лимиты: макет ≤ 8 МБ, ≤ 200 экранов, ≤ 1000 узлов на экран, ≤ 5000 узлов на макет; размеры экрана 40–4000 px. Есть rate-limit.

window.Maketa (в редакторе)

В открытом редакторе доступен объект window.Maketa — им пользуются встроенные ИИ-сценарии и автотесты. Основное:

Maketa.schema                   // "maketa.board.v2"
Maketa.getDocument()            // вся сцена целиком
Maketa.setDocument(doc)         // заменить сцену
Maketa.listScreens()            // [{id,name,kind,locked,nodes,objects,parentId,branches,...}]
Maketa.setActiveScreen(id)
Maketa.createScreen(name, dev)  // → screenId
Maketa.branchScreen(id)         // ветка экрана
Maketa.importAiTree(screen, asScreen, name)  // дерево {slots,nodes} → в макет
Maketa.exportPNG()

Методы listElements, addElement, updateElement, moveElement, removeElement остались от плоской модели и работают только на экранах старого формата: у экрана-дерева объектов нет. Дерево правится через MCP (maketa_add_node и родня) или целиком через setDocument.

Модель maketa.board.v2

Экран — это дерево, а не список фигур. В прошлой модели каждый объект знал свои x и y, и человек расставлял их на глаз. Теперь у узла нет координат вовсе: он лежит внутри родителя, а родитель решает, где ребёнок окажется и сколько места ему дать. Правило дословно взято у Flutter, Compose и SwiftUI:

Ограничения идут вниз. Размеры идут вверх. Родитель задаёт положение.

Свободное размещение никуда не делось — это раскладка over, в которой ребёнок прижимается к одной из девяти точек родителя со смещением dx/dy.

Экран: слоты и карта узлов

У экрана четыре места. top — шапка, она не прокручивается. bottom — нижнее меню, оно тоже не прокручивается. body — всё остальное, оно занимает остаток высоты и прокручивается. over — слой поверх экрана: плавающая кнопка, подсказка, шторка.

Сами узлы лежат в плоской карте nodes, а дерево внутри неё задают массивы children. Карта, а не вложенные объекты, потому что узел адресуется по id — из действий, из панели структуры, из MCP и из истории отмен; искать его обходом дерева на каждый чих дорого.

{
  "v": 2,
  "name": "Доставка еды",
  "screens": [{
    "id": "s_menu", "name": "Меню",
    "device": "iphone-15", "w": 393, "h": 852, "bg": "#FFFFFF",
    "safe": true,                      // учитывать вырез и полосу-домой
    "kind": "design",                  // design | code
    "slots": {
      "top":    "n_hdr",               // шапка — не прокручивается
      "body":   "n_body",              // содержимое — прокручивается
      "bottom": "n_nav",               // нижнее меню — не прокручивается
      "over":   ["n_fab"]              // слой поверх
    },
    "nodes": {
      "n_hdr":  {"id":"n_hdr","type":"stack","name":"Шапка","w":"fill","h":56,
                 "look":{"bg":"#FFFFFF","border":"#ECEDF2","borderW":1},
                 "layout":{"dir":"right","pad":[0,16,0,16],"gap":8,"cross":"center"},
                 "children":["n_ttl"]},
      "n_ttl":  {"id":"n_ttl","type":"text","content":{"text":"Меню","size":22,"weight":700}},
      "n_body": {"id":"n_body","type":"stack","w":"fill","h":"fill",
                 "layout":{"dir":"down","pad":16,"gap":12,"scroll":true},
                 "children":["n_card"]},
      "n_card": {"id":"n_card","type":"stack","name":"Карточка блюда","w":"fill",
                 "repeat":5,                       // пять одинаковых карточек одним узлом
                 "look":{"bg":"#F7F8FA","radius":14,"shadow":"soft"},
                 "layout":{"dir":"down","pad":12,"gap":4},
                 "children":["n_name","n_price"],
                 "action":{"t":"goto","screen":"s_dish","mode":"modal"}},
      "n_name": {"id":"n_name","type":"text","content":{"text":"Пицца «Маргарита»","size":16,"weight":600}},
      "n_price":{"id":"n_price","type":"text","content":{"text":"590 ₽","size":14,"color":"#6B7280"}},
      "n_nav":  {"id":"n_nav","type":"tabs","w":"fill","h":82,
                 "content":{"variant":"bottom","active":0,
                            "items":[{"text":"Меню","icon":"🍕"},
                                     {"text":"Корзина","icon":"🛒","link":"s_cart"}]}},
      "n_fab":  {"id":"n_fab","type":"button","w":56,"h":56,
                 "at":"bottom-right","dx":-16,"dy":-100,
                 "look":{"bg":"#3A5BD9","radius":28},
                 "content":{"text":"+","kind":"filled"}}
    }
  }]
}

Узел

Общие поля: id (1–40 символов [A-Za-z0-9_-], уникален в пределах экрана), type, name (человеческое имя — его видно в панели структуры), children (id детей по порядку, только у контейнера), w и h, пределы wMin/wMax/hMin/hMax, grow, layout, look, content, action, hidden, repeat. Внутри контейнера с dir: "over" добавляются at и dx/dy.

Полей x, y, margin, flex, zIndex не существует: положение — дело родителя, внешний отступ — это gap родителя, порядок отрисовки — позиция в children.

Размер по каждой оси — три значения: "hug" — по содержимому (умолчание, поле можно не писать), "fill" — занять всё свободное место родителя, число — фиксированный размер в px. Высоту надписи и картинки числом задавать не нужно: у надписи это "hug" плюс content.lines, у картинки — "hug" плюс content.ratio. Число, снятое с одного телефона, на другом ломает экран.

Пределы wMin, wMax, hMin, hMax (число px, 0–10000) — «не уже» и «не шире». Это надстройка над w/h, а не четвёртый режим: сначала считается размер по правилу w/h, потом он зажимается пределами (при противоречии сильнее минимум — как в CSS), и только потом — местом родителя. Предел сужает, но не перебивает родителя: «не уже 200» внутри рамки 150 даст 150. Колонка текста, читаемая и на телефоне, и на 1440 px, — это w: "fill" с wMax: 640.

grow (целое 0–100, умолчание 1) — вес, по которому делится свободное место между детьми "fill" одного ряда или столбца: поиск с grow: 3 рядом с фильтром grow: 1 займёт втрое больше. grow: 0 — «взять свою базовую долю и не тянуться дальше». Без веса «поиск шире фильтров» приходилось писать пикселями, а от пикселей мы и уходим.

Девять типов

Карточка, список, сетка, лента, шапка, разделитель, чип, аватар — не типы, а заготовки палитры, собранные из этих девяти.

ТипЧто этоcontent
stackКонтейнер: блок, который раскладывает детей вниз, вбок, поверх или сеткой с переносом. Из него собирается всё остальноеslots: {header} — id надписи-заголовка секции, more: true — ссылка «Все →», ratio — пропорция блока вместо высоты числом
textНадписьtext (≤5000), size 8–200, weight 100–900 сотнями, color, align left|center|right, lines — предел числа строк 1–50 (высота при этом "hug", лишнее прячется многоточием), emoji: true — текст-иконка
imageКартинка или её местоsrc — только data:image/… (≤2 МБ), fit cover|contain|fill, ratio — отношение ширины к высоте (16/9 ≈ 1.78) вместо высоты числом
buttonКнопка (тип, а не прямоугольник с текстом)text, icon — эмодзи, kind filled|tonal|text, size, weight, color, align, ratio
inputПоле вводаplaceholder, value, search: true — лупа слева, kind text|password|multiline
choiceПереключатели или галочки одной группойitems — до 20 × {text, on}, multi: true — галочки вместо «один из», active
tabsВкладки и нижнее меню одним узломitems — до 8 × {text, icon, link — id экрана, shows — id блока, action}, active (0-based), variant bottom|pill|underline|segmented, color, muted, activeText
itemПункт списка: значок, заголовок, подпись, элемент справаslots: {lead, title, sub, trail} — это роли над уже существующими узлами экрана («вот этот здесь за значок»), а не отдельные дети: один и тот же узел законно стоит и в children, и в роли; ratio — пропорция вместо высоты числом
screenСам экран в рамке устройства—

Раскладка (layout) — только у контейнера

ПолеЗначенияЧто делает
dirdown · right · over · wrapВниз (умолчание), вбок, поверх (свободное размещение), сеткой с переносом
padчисло или [верх, право, низ, лево]Внутренние отступы блока, 0–400
gapчислоЗазор между детьми. Внешних отступов у ребёнка нет — зазор задаёт только родитель. В ленте зазор съедает место, а не ширину карточки: сколько карточек видно, столько и видно
runGapчисло 0–400Зазор между рядами в сетке с переносом (dir: "wrap"). Не задан — как gap
mainstart · center · end · between · around · evenlyКак дети распределяются вдоль оси dir
crossstart · center · end · stretchВыравнивание поперёк; stretch распрямляет детей с размером hug на всю ширину
scrolltrueСодержимое прокручивается по оси dir
cols1–12Колонок в сетке (dir: "wrap")
visible2 · 2.5 · 3 …Ширина карточки в горизонтальной ленте задаётся так, а не пикселями: сколько карточек видно в кадре. Половинка означает «край следующей виден» — подсказка, что список листается. Единственная формулировка, переживающая смену устройства

При dir: "over" ребёнок ставится полем at — одна из девяти точек (top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right) — и смещением dx, dy.

Ещё два значения at держат узел за оба края сразу: left-right — тянуться от левого края до правого, top-bottom — от верхнего до нижнего. Это Figma-констрейнты «Left and Right» / «Top and Bottom» и Positioned(left:, right:) во Flutter; при растяжке смещение становится отступом с обеих сторон. Само смещение dx/dy — либо число px, либо доля родителя строкой: "12%" (от −200 % до 200 %). Пиксельная привязка к левому верхнему углу — худший из вариантов: на узком экране правый край уезжает.

Оформление (look)

bg, border (hex, none или transparent), borderW 0–12, radius — число или [tl,tr,br,bl], shadow — none | soft | card, opacity 0–1. Тень — три состояния, а не число: человеку нужно «плоско / чуть приподнято / карточка», а elevation в dp в вебе означает другое.

Действия, скрытые блоки и повторитель

  • action: {"t":"goto","screen":"s_cart","mode":"push|modal|dialog|replace"} — открыть экран целиком, шторкой снизу, окном по центру или заменой без возврата. Отдельного поля linkMode в v2 нет: режим лежит в action.mode.
  • {"t":"back"} — закрыть экран и вернуться на предыдущий: крестик «✕» и стрелка «‹». Куда именно вернуться, решается в момент нажатия, поэтому один и тот же экран можно открывать из разных мест.
  • {"t":"show","show":["n_paneB"],"hide":["n_paneA"]} — переключить блоки на том же экране: вкладки, шаги формы, раскрытие. Копии экрана для этого не нужны.
  • hidden: true — блок лежит в дереве, но не показывается, пока его не откроет такое действие.
  • repeat: N (до 50) — нарисовать узел N раз подряд внутри родителя. Список из десяти строк — один узел с repeat: 10: правится образец, меняются все.

Правила целостности

Ошибки дерева не видны глазами и ломают экран уже у зрителя, поэтому они проверяются на входе и возвращаются отказом с указанием узла, а не чинятся молча: «исправленный» сервером экран человек увидит не своим.

КодЧто нарушено
bad_node_typeТип не из девяти (в том числе rect, ellipse, path, note из старой модели)
unknown_nodeСсылка на узел, которого нет: в children, в слоте, в действии, в роли пункта
node_two_parentsУзел встречается ребёнком дважды или стоит и в слоте, и внутри блока
tree_cycleУзел оказался собственным предком
too_deepВложенность глубже 12 уровней
unknown_screenДействие или пункт меню ведёт на несуществующий экран
bad_nodesУ документа "v": 2, а у экрана вместо карты nodes лежит старый objects

А вот негодные значения полей отказом не считаются: цвет «не-цвет», visible: 300, repeat: 999 и лишние поля молча заменяются умолчанием или обрезаются по границе — ронять сохранение всего макета из-за одного числа незачем.

Лимиты: макет ≤ 8 МБ, ≤ 200 экранов, ≤ 1000 узлов на экран, ≤ 5000 узлов на макет, глубина ≤ 12, размеры экрана 40–4000 px.

Полная спецификация модели и обоснование решений — в репозитории: docs/research/constructor/SPEC-model-v2-2026-09-04.md. Движок раскладки, который считает из дерева координаты, — /layout-engine.js (без зависимостей, работает и в браузере, и в Node).

Для ИИ-агентов

Если вы ИИ и нашли эту страницу: Maketa — программируемый конструктор экранов. Вы можете:

  • Работать полностью автономно: песочный ключ через POST /agent/register — без человека, почты и оплаты.
  • Подключиться MCP-сервером (OAuth или headless по ключу) и вызывать инструменты maketa_*.
  • Ходить прямо в REST API — схема /openapi.json; постоянный проектный ключ выдаёт владелец доски в кабинете.
  • Читать и писать сцену в модели maketa.board.v2 — экран деревом узлов.

Краткая машиночитаемая сводка — /llms.txt, подробная — /llms-full.txt. Правило: code-экраны не редактируются напрямую — создавайте ветку.

Поддержка

Вопросы, доступ к проектам, нативный веб-адаптер: Telegram @demda · сайт студии demda.pro.

Maketa — продукт студии заказной разработки demda.pro. Нужен не только макет, а готовое приложение — приходите к нам.