Архитектура Builder-web
Расширенное описание внутреннего устройства визуального редактора, потоков данных, состояния, валидации, превью, expression‑DSL, подсветки ошибок, системы моков для источников данных и AI‑ассистента.
🎯 Цель страницы
Эта страница подробно объясняет архитектуру @lowcode/builder-web, включая:
- структуру каталогов и роль каждого модуля;
- устройство центрального состояния редактора (
EditorState); - формирование и обновление DSL‑дерева;
- работу панелей PropertiesPanel / StatePanel / EventHandlersPanel / DataSourcesPanel / AiAssistPanel;
- механизм expression‑валидации (syntax + semantic);
- превью‑pipeline (DSL → AST → TSX → JS → runtime-core);
- механизм подсветки ошибок и связь Canvas ↔ PreviewPanel;
- интеграцию с
@lowcode/runtime-coreи devtools‑событиями; - поддержку моков (mock) для
DataSourceв DSL и превью; - интеграцию с AI‑оркестратором и панелью AI‑ассистента;
- принципы проектирования всего редактора.
Документ предназначен как глубокое архитектурное описание.
1. Общая роль Builder-web
builder-web — это визуальная IDE для работы с DSL‑моделью приложения. Он объединяет:
- графический редактор UI (Canvas);
- редактор свойств (PropertiesPanel v2);
- редактор состояния (StatePanel);
- редактор событий и действий (EventHandlersPanel);
- редактор источников данных и моков (DataSourcesPanel);
- панель AI‑ассистента (AiAssistPanel) поверх Canvas/Preview;
- runtime‑превью, исполняющее скомпилированный DSL через
@lowcode/runtime-core; - подсветку ошибок DSL и expression‑валидации;
- панель инспектора runtime‑состояния и событий;
- историю действий с Undo/Redo и авто‑сохранением, чтобы любые правки (включая AI‑операции) можно было откатить горячими клавишами (
Cmd/Ctrl+Z,Cmd/Ctrl+Y).
Builder-web работает полностью локально в браузере:
- структура DSL валидируется через
@lowcode/dsl; - выражения валидируются как по синтаксису, так и по семантике (DSL + AST);
- React‑код генерируется через
@lowcode/dsl-compiler; - TSX‑бандл исполняется через общий движок
@lowcode/runtime-core; - Canvas, панели и превью синхронно отражают состояние одного и того же DSL;
- для
DataSourceв превью используются только моки (mock‑конфигурация в DSL), реальные HTTP‑запросы из builder-web не выполняются; - AI‑ассистент работает поверх текущего DSL, но не генерирует код напрямую — он возвращает список декларативных операций (
AiOperation), которые применяются кAppSchemaна стороне оркестратора.
Основные зависимости:
@lowcode/dsl— структура DSL, валидация,ExpressionValue,DataSource+DataSource.mock;@lowcode/dsl-compiler— AST, expression‑анализ, TSX‑генерация (учитывает списокdataSourcesи их mock‑конфигурацию);@lowcode/runtime-core— исполняющий движок (bundle → modules →RuntimeInstance, в т.ч. default executor с поддержкой моков);@lowcode/ui-kit— UI‑компоненты редактора (панели, карточки, LayoutShell и т.п.);@lowcode/ai-orchestrator— формирование промпта, строгий JSON‑формат операций и применениеAiOperationк DSL.
Исторически превью использовало @babel/standalone напрямую, но в текущей архитектуре вся ответственность за компиляцию и исполнение бандла вынесена в @lowcode/runtime-core, а builder-web концентрируется на UX и настройке DSL (включая моки и интеграцию с AI).
2. Структура каталога builder-web
apps/builder-web/src/
├─ api/ # HTTP‑клиент для @lowcode/api
├─ ai/ # хуки и утилиты для AI‑ассистента
├─ auth/ # AuthContext, AuthGate, AuthScreen (логин/регистрация)
├─ account/ # AccountContext и хуки профиля/подписки
├─ components/ # Canvas, PreviewPanel, LayoutShell, панели редактора
├─ expressions/ # validateExpression, редактор выражений (CodeMirror), контексты
├─ state/ # EditorState, централизованная логика DSL
├─ data/ # шаблоны DSL
├─ pages/ # страницы SPA (home/editor/...)
├─ styles/ # глобальные стили, Tailwind-кастомизация
├─ types.ts # общие типы для редактора
├─ App.tsx # корневой компонент SPA
└─ main.tsx # точка входа Vite/React
2.1. state/ — EditorState
Здесь живёт центральное состояние редактора — EditorState:
- текущий
app: AppSchema | null; activePageId: string | null— активная страница;activeBlockId: string | null— активный custom‑блок (в режиме Blocks);isEditingBlocks: boolean— флаг режима редактирования блоков;selectedNodeId: string | null— выбранный компонент в Canvas;issueNodeIds: string[]— ID компонент с ошибками (DSL/выражения/runtime);- состояние панелей (раскрытые секции, выбранные вкладки и т.п.);
- вспомогательные флаги/поля (загрузка, сохранение, источники шаблонов и т.п.).
- внутренняя история изменений DSL (стеки past/future) для Undo/Redo, синхронизированная с автосохранениями и AI‑операциями.
Также в state/ находятся:
- хук
useEditorState()(контекст редактора); - pure‑функции для работы с DSL‑деревом (поиск, обновление, вставка/удаление);
- хелперы для работы с
app.dataSourcesи mock‑конфигурацией (создание/удаление/обновление источников и их моков).
2.2. components/
Основные визуальные блоки:
LayoutShell.tsx— каркас редактора (верхний бар, левая колонка с Canvas, правая с панелями, нижнее превью). В хэдэре отображаются кнопки Undo/Redo, переключатель темы, кнопка Settings (открывает общую модалку настроек с управлением авто‑сохранением и переходом в Account Settings);Canvas.tsx— дерево компонентов DSL (визуализацияComponentNode), поддерживает базовые layout-компоненты (Container/Grid/Repeat/If);PreviewPanel.tsx— превью на базе@lowcode/runtime-core;PropertiesPanel.tsx— PropertiesPanel v2 (динамически строится поComponentDefinition);BlocksPanel.tsx— управление custom‑блоками (создание/переименование/props) в режиме Blocks;StatePanel.tsx— редакторappState/page.state;EventHandlersPanel.tsx— редактор событий и Actions;DataSourcesPanel.tsx— редактор источников данных и mock‑конфигурации;RuntimeStateInspector.tsx— панель просмотра текущегоRuntimeSnapshot;AiAssistPanel.tsx— панель AI‑ассистента над Canvas/Preview;SettingsModal.tsx— централизованные настройки редактора (auto-save интервал, переход в Account Settings);MediaLibrary/— модальное окно для управления attachments проекта:MediaLibraryModal.tsx— основная модалка с тремя режимами (browse/details/upload);MediaLibraryGrid.tsx— сетка превью файлов с фильтрацией по MIME type;MediaLibraryDetails.tsx— детальный просмотр выбранного attachment;MediaLibraryUpload.tsx— форма загрузки файлов в S3 или добавления ссылок;MediaLibraryHeader.tsx— верхняя панель с поиском и переключением режимов.
- вспомогательные компоненты (toolbar'ы, списки, формы ввода и т.п.).
2.3. expressions/
Слой локальной syntax-валидации выражений в стиле:
validateExpressionSyntax(expression: string): {
isValid: boolean;
errors: string[];
};
Дополнительно здесь находится контекст редактора выражений (ExpressionEditorContext) и хелперы для сборки контекста (variables, dataSources, event).
Используется для:
- проверки полей ввода выражений в PropertiesPanel/StatePanel;
- быстрой подсветки синтаксических ошибок до запуска
dsl-compiler; - дополнения диагностик, которые приходят из
validateExpressionsInAstApp.
2.4. api/
API‑клиент для доступа к сервису @lowcode/api:
- запрос списка проектов;
- загрузка/сохранение версии проекта;
- запрос на валидацию DSL;
- вызов AI‑ассистента через
POST /ai/assist(callAiAssist).
С появлением клиентских аккаунтов client.ts также отвечает за:
- регистрацию/логин (
/auth/register,/auth/login,/auth/logout,/auth/refresh); - хранение access/refresh токенов в
localStorage(lowcode:auth); - автоматическую подстановку
Authorization: Bearer <token>для всех запросов; - silent-refresh access‑токена при
401, используя refresh‑токен; - получение профиля аккаунта (
/account/profile) и обновление пользовательских/клиентских настроек.
Это означает, что любой код редактора, который вызывает listProjects, loadProject, saveProject и т.п., уже получает client-scoped данные без необходимости передавать clientId. Единственное требование — перед началом работы вызвать registerClientAccount или login, чтобы токены попали в client.ts.
2.5. ai/
Слой интеграции с AI‑оркестратором:
useAiAssist.ts— хук верхнего уровня для сценария AI‑ассистента;- утилиты/типы для общения с backend‑API;
- именно здесь задаются дефолтные значения AI‑провайдера и модели (см. раздел про AI‑ассистент).
2.6. auth/ и account/
Новые директории отвечают за всю клиентскую работу с аккаунтами:
-
auth/содержитAuthContext,AuthGateиAuthScreen.
AuthProviderхранит текущуюAuthSession, автоматически обновляет access‑токен по refresh‑токену и предоставляетlogin,register,logout,refreshSession.
AuthGateблокирует загрузку редактора до тех пор, пока не будет либо подтверждена сессия, либо показанаAuthScreen.
AuthScreenреализует UI логина и создания клиента/воркспейса (slug + владелец) и использует UI‑kit. -
account/содержитAccountContext, который поверхAuthContextзагружает профиль пользователя, клиента и историю подписок (/account/profile,/account/subscription,/account/subscription/history) и предоставляет методыupdateProfile,changePassword,updateClientSettings,refreshSubscription. -
Компонент
AccountSettingsModalразмещён вcomponents/и используетAccountContextдля построения UI личного кабинета (вкладки Profile/Workspace/Subscription + logout). -
App.tsxподключает эту инфраструктуру: слева остаётся редактор, а в правой части хэдера черезLayoutShell.headerRightExtrasвыводится кнопка аккаунта с бейджем тарифа. При клике открываетсяAccountSettingsModal.
Тестовый аккаунт по умолчанию
Для локальной разработки заведён клиент default-client с владельцем:
Email: admin@default.local
Password: DevPass123
Эти данные подходят для входа в builder-web сразу после запуска backend’а и дают доступ ко всем мигрированным проектам.
3. EditorState — центральная модель данных
EditorState — ядро builder-web. Он объединяет:
- DSL‑модель (
app: AppSchema); - текущий UI‑контекст (активная страница, выбранный компонент);
- список проблемных компонент (
issueNodeIds); - настройки редактора (например, какой режим отображения включён);
- конфигурацию источников данных и моков (
app.dataSources+DataSource.mock); - точки интеграции с превью (через
PreviewPanel); - интеграцию с AI‑ассистентом (передача
app,projectId,pageId,hints).
3.1. DSL‑дерево как источник истины
DSL полностью хранится в памяти в виде AppSchema:
interface AppSchema {
id: string;
name: string;
version: 1;
pages: PageSchema[];
appState?: AppStateSchema;
dataSources?: DataSource[];
eventHandlers?: EventHandler[];
// ...
}
Любое изменение в интерфейсе (поменяли текст кнопки, добавили состояние, настроили обработчик события или mock для DataSource, применили операции AI‑ассистента) приводит к иммутабельному изменению AppSchema:
- создаётся новая структура
AppSchemaс применённым патчем; - запись производится в EditorState;
- Canvas, панели и PreviewPanel получают новый
appи перерисовываются.
3.2. Иммутабельные обновления
Обновления DSL реализованы как pure‑функции:
setApp((prev) =>
updateComponentNode(prev, nodeId, (node) => ({
...node,
props: {
...node.props,
text: 'New label',
},
})),
);
и аналогично для источников данных:
setApp((prev) =>
updateDataSource(prev, dataSourceId, (ds) => ({
...ds,
mock: {
...ds.mock,
enabled: true,
mode: 'static',
value: { items: demoUsers },
},
})),
);
или при применении результата AI‑ассистента:
if (response.updatedAppSchema) {
setApp(response.updatedAppSchema);
}
Это гарантирует:
- отсутствие неожиданных сайд‑эффектов;
- корректный re-render в React;
- прозрачную историю изменений (в будущем можно будет навинтить undo/redo).
3.3. Связь EditorState ↔ панели
- Canvas читает
app+activePageId+issueNodeIds. - PropertiesPanel читает
app+selectedNodeIdи обновляет только конкретный компонент. - StatePanel обновляет
app.appStateиpage.state. - EventHandlersPanel обновляет
app.eventHandlersиpage.watchers. - DataSourcesPanel обновляет
app.dataSourcesи их полеmock. - AiAssistPanel читает
app,projectId,activePage,selectedNodeи вызываетcallAiAssist, который возвращает обновлённыйAppSchema. - PreviewPanel читает весь
appиactivePageId, но не меняет DSL напрямую (все изменения идут только через EditorState).
4. Панели редактора
4.1. Canvas
Canvas визуализирует дерево компонентов DSL (ComponentNode). Основные функции:
- отображение иерархии
rootComponent/layoutComponentдля активной страницы; - переключение режима Page/Shell: в режиме Shell редактируется
app.shell, а в дереве появляетсяPageOutletкак слот для страницы; - выбор компонента (устанавливает
selectedNodeIdв EditorState); - подсветка ошибочных нод (если
node.idнаходится вissueNodeIds); - базовые layout‑индикаторы.
В будущем Canvas станет полноценным drag‑and‑drop редактором, но уже сейчас он чётко связан с DSL и системой ошибок.
4.2. PropertiesPanel
PropertiesPanel v2 строится динамически на основе реестра компонентов в @lowcode/dsl:
-
для выбранного
ComponentNodeберётся егоtype; -
через реестр
ComponentDefinitionRegistryполучаемComponentDefinition:- список пропов,
- типы (
string/number/boolean/any/expressionи т.п.), - default‑значения,
- метаданные UI (placeholder, multiline, format…);
-
на основе этого описания формируется форма ввода.
Каждый проп может быть в одном из режимов:
- StaticValue (
string,number,boolean, массивы); - ExpressionValue (
{ kind: 'expression', expression: string }).
PropertiesPanel:
- переключает режим (static ↔ expression);
- приводит строковый ввод к правильному типу (число, boolean);
- вызывает локальный
validateExpressionSyntaxдля expression‑полей; - пишет результат обратно в DSL (в
node.props).
Во вкладке Layout & Style используется LayoutStylePanel:
- редактирует
layoutиstyleкомпонента; - секция Interaction states пишет hover‑параметры в
styleStates.hover(transformScale,opacity,rotateDeg,transitionMs,transitionEasing,transitionProperty).
Специальная обработка для Image и Video компонентов:
Когда пользователь редактирует src проп компонента Image или Video, PropertiesPanel предоставляет специальный UI для работы с медиа-библиотекой:
-
MediaLibrary Modal - модальное окно для управления attachments проекта:
- Просмотр сетки загруженных файлов (browse mode)
- Детальный просмотр выбранного attachment (details mode)
- Загрузка новых файлов в S3 или добавление ссылок (upload mode)
- Поиск по имени файла
- Фильтрация по MIME type
-
Фильтрация по типу медиа:
В PropertiesPanel (Select):
- Для компонента Image: показываются только изображения
- Для компонента Video: показываются только видео
- Фильтрация определяется по MIME type или расширению файла
- Для backward compatibility показываются файлы с неопределенным типом
В MediaLibrary Modal:
- Три таба для фильтрации: All / Images / Videos
- По умолчанию показываются все файлы (таб "All")
- Пользователь может переключиться на "Images" или "Videos" для удобства просмотра
- Ограничение выбора:
- Если модалка открыта для Image компонента (
mimeTypeFilter='image/*'):- Можно просматривать все файлы
- Кнопка "Select" активна только для изображений
- Для видео кнопка disabled с сообщением "Only images can be selected"
- Аналогично для Video компонента
- Если модалка открыта для Image компонента (
Определение типа файла:
- Приоритет 1: MIME type из БД (для S3 uploads)
- Приоритет 2: Расширение файла в
downloadUrlquery параметреfilename=(для Яндекс.Диск) - Приоритет 3: Расширение в
urlилиsourceUrl - Приоритет 4: Расширение в поле
name
-
UI компонентов:
- Select с списком совместимых attachments (только изображения для Image, только видео для Video)
- Кнопка "Edit" для открытия MediaLibrary modal со всеми файлами
- Превью выбранного файла:
- Для Image: компонент ImagePreview
- Для Video: встроенный
<video>элемент с controls
-
Процесс загрузки файла:
// 1. Запрос presigned URL (5% → 15%)
const { uploadUrl, storageKey } = await requestUploadUrl({
projectId,
filename: file.name,
contentType: file.type,
});
// 2. Загрузка в S3 с отслеживанием прогресса (15% → 85%)
await uploadFileToS3(uploadUrl, file, file.type, (s3Progress) => {
// s3Progress: 0-100, масштабируем в диапазон 15-85%
const scaledProgress = 15 + Math.round((s3Progress / 100) * 70);
setUploadProgress(scaledProgress);
});
// 3. Создание записи attachment (85% → 95%)
const attachment = await createS3Attachment({
projectId,
storageKey,
name: file.name,
mimeType: file.type,
size: file.size,
});
// 4. Запись UUID в src проп (95% → 100%)
onChange(attachment.id);Отслеживание прогресса:
- Функция
uploadFileToS3используетXMLHttpRequestвместоfetchдля поддержки событий прогресса xhr.upload.addEventListener('progress')предоставляет реальный прогресс загрузки в S3- Прогресс распределяется: 5% (подготовка) → 15% (получение URL) → 85% (загрузка в S3) → 95% (создание записи) → 100% (завершение)
- Функция
-
Поддержка двух режимов источника:
sourceType='url'- UUID link-attachment (короткая публичная ссылка хранится вsourceUrl)sourceType='upload'- UUID S3 attachment
-
Ограничения размера файлов:
- Изображения: максимум 10 МБ
- Видео: максимум 100 МБ
ВАЖНО:
projectIdберется изuseEditorState()(UUID проекта), а НЕ изapp.id(который содержит DSL schema ID вида"app-blank")sourceTypeпроп скрыт от пользователя в UI (автоматически определяется по форматуsrc)- MediaLibrary компоненты находятся в
apps/builder-web/src/components/MediaLibrary/ - Для Yandex Disk можно вставлять короткую публичную ссылку — система автоматически получит прямую ссылку на скачивание.
- В деталях Media Library показываются обе ссылки: public (sourceUrl) и direct (downloadUrl).
- Для
Image.src/Video.srcв режиме expression внутриRepeatдоступна кнопка Pick attachment:- открывает MediaLibrary;
- подставляет выражение
item.imageId(для Image) илиitem.videoId(для Video); - автоматически выставляет
sourceType="upload"; - показывает helper‑текст о необходимости хранить UUID в данных.
4.3. StatePanel
StatePanel работает с двумя уровнями состояния:
app.appState.variables— глобальное состояние приложения;page.state.variables— локальное состояние конкретной страницы.
Возможности:
- создание/удаление переменных;
- изменение типа (
string/number/boolean/any); - задание
initialValueс учётом типа; - флаг
required; - типобезопасная запись в DSL (корректные JSON‑значения).
Именно отсюда приходят переменные state.*, которые затем доступны в выражениях (ExpressionValue) и попадают в контекст выражений AST.
4.4. EventHandlersPanel
EventHandlersPanel управляет массивом EventHandler[] в DSL:
interface EventHandler {
id: ID;
targetComponentId?: ID;
targetPageId?: ID;
eventName: string;
actions: Action[];
}
Панель позволяет:
-
привязать событие к компоненту (
onClick,onChangeи т.п.); -
добавить цепочку Actions:
navigate;setState(глобальное/по странице);callDataSource;showToast;logи др.;
-
задавать параметры Actions, в том числе через
ExpressionValue.
С точки зрения runtime, эти декларативные Actions превращаются в вызовы actions.* и/или RuntimeCommand, обрабатываемые @lowcode/runtime-core.
В правом сайдбаре есть отдельная вкладка Watchers (панель WatchersPanel) для page.watchers[]:
when(ExpressionValue) — выражение, за изменением которого нужно следить;actions[]— что запускать при изменении;mode(change|always),debounceMs,throttleMs. Режимalwaysдоступен только при ненулевом debounce или throttle.
Watchers исполняются runtime-core на стороне сгенерированного App.tsx (dsl-compiler), поэтому поведение одинаковое в превью и runtime-host.
При редактировании действий можно использовать event.* внутри выражений. В превью builder-web event имеет нормализованный вид:
{
type: string;
value?: unknown;
target?: {
value?: unknown;
name?: string;
id?: string;
};
originalEvent?: unknown;
}
Для Input.onChange удобно использовать event.value или event.target.value.
4.5. Expression editor (modal)
Во всех местах, где поддерживаются ExpressionValue, доступен режим редактирования через модалку:
- открывается из
ExpressionInput(маленькая кнопка "Edit"); - редактор построен на CodeMirror 6 и живёт локально в модалке;
- Save по кнопке или
Ctrl/Cmd+S, Undo/Redo —Ctrl/Cmd+Z/Ctrl/Cmd+Y; - подсветка синтаксиса, линтер и подсказки автодополнения;
TabиEnterподтверждают автодополнение;- показывается список доступных глобальных переменных (
state,data,props,event) с контекстными ограничениями (например,eventдоступен только в хендлерах).
Редактор также использует токены темы (ThemeProvider), поэтому одинаково выглядит в светлой и тёмной теме.
4.5. DataSourcesPanel и моки
DataSourcesPanel работает с app.dataSources и их mock‑конфигурацией.
4.5.1. Структура DataSource
Упрощённо, в DSL есть два типа источников данных:
export type DataSource = StaticDataSource | RestDataSource;
и общее поле mock:
interface DataSourceMockConfig {
enabled: boolean;
activeScenario?: 'success' | 'error';
successMock?: {
mode: 'static' | 'sequence';
value?: StaticValue;
sequence?: StaticValue[];
sequenceValueType?: 'json' | 'text';
delayMs?: number;
};
errorScenarios?: {
api?: {
kind: 'api';
statusCode?: number;
payload?: StaticValue;
delayMs?: number;
};
network?: {
kind: 'network';
message?: string;
statusCode?: number;
statusText?: string;
delayMs?: number;
};
activeKind?: 'api' | 'network';
};
}
DataSourcesPanel позволяет:
- создавать/удалять источники данных;
- настраивать REST/STATIC поля источника данных (baseUrl, path, method, headers, queryParams, body, timeoutMs);
- включать/выключать
mock.enabled; - настраивать две независимые секции мока: Success response (static / sequence) и Error response (API/Network);
- выбирать активный сценарий через SegmentedControl (переключатель success/error) и тип ошибочного сценария (
errorScenarios.activeKind); - задавать
valueилиsequence, тип элементовsequenceValueType, а также задержки для каждого сценария. - для JSON‑payload с
items[]есть UI‑помощник для выбора attachments и сохранения UUID вitems[].imageId(mock success и static data).
4.5.1.1. REST редактор: headers/query/body/timeout
В UI редактора REST‑источника используются компактные пары key/value:
- Query params (
dataSource.config.queryParams) и Headers (dataSource.config.headers) хранятся какRecord<string, PropValue>. - Поле value вводится через
ExpressionInput:- если введённый текст можно распарсить как JSON (
JSON.parse) — он сохраняется как StaticValue (например,123,true,"text",{ "a": 1 }); - иначе значение сохраняется как ExpressionValue (
{ kind: "expression", expression: "..." }) и вычисляется в runtime.
- если введённый текст можно распарсить как JSON (
Таким образом, чтобы задать строковый литерал, нужен валидный JSON (включая кавычки), например "runtime-host".
Если кавычки не использовать, строка будет интерпретирована как expression.
Поле Body (dataSource.config.body) также поддерживает ввод expression/JSON (в зависимости от выбранного режима в UI) и подсветку синтаксических ошибок.
Поле Timeout (dataSource.config.timeoutMs) задаётся в миллисекундах и применяется только в runtime-host (в превью — только моки).
Важно:
ExpressionValue.expression— это выражение, а не набор операторов; символ;в конце выражения приведёт к синтаксической ошибке (см.apps/docs/docs/architecture/expression-parser.md).
Поле sequenceValueType фиксирует, как именно пользователь хочет трактовать элементы последовательности: как JSON (json, поведение по умолчанию) или как текст (text, без автопарсинга). Это позволяет избавиться от неоднозначного поведения кнопки Evaluate All.
4.5.2. Поведение моков в превью
В превью builder-web используется default executor из @lowcode/runtime-core:
-
если
mock.enabled === true, executor нормализует конфигурацию кsuccessMockиerrorScenariosи выбирает сценарий:- дефолт —
mock.activeScenario(если нет override); RuntimeStateInspectorили ручной вызов черезcallDataSourceмогут передатьmockScenarioOverride, который сохраняется в__mockScenarioдля devtools;- success-сценарий возвращает
valueлибо элементыsequence(циклически, поdataSourceId, с учётомsequenceValueType); - error-сценарий выбрасывает ошибку с пользовательским
messageили дефолтным текстом (а runtime фиксирует её в__lastError).
- дефолт —
-
если
mock.enabled !== true:- для
kind="static"возвращаетсяconfig.data; - для
kind="rest"и других не‑static‑видов в превью выбрасывается явная ошибка о том, что REST data source без mock не может быть выполнен в превью.
- для
Таким образом:
- builder-web никогда не делает реальные HTTP‑запросы из превью;
- все данные для
DataSourceберутся из mock‑конфигурации илиconfig.data; - пользователь может быстро переключаться между success/error сценариями прямо в DataSourcesPanel и в RuntimeStateInspector, мгновенно проверяя оба варианта;
- если REST-источник не замокан, пользователь получает понятное сообщение о необходимости настроить mock.
4.6. AiAssistPanel
AiAssistPanel — лёгкая панель поверх Canvas/Preview, отвечающая за сценарий:
- пользователь вводит текстовый запрос (на естественном языке);
- панель собирает контекст (
app,projectId, активную страницу, текущий selection); - формируется
AiAssistRequestи отправляется на backend (POST /ai/assist); - в ответ приходит
AiAssistResponseсupdatedAppSchemaи текстовымexplanation; - если
updatedAppSchemaприсутствует, он заменяет текущийappв EditorState`.
Основные файлы:
apps/builder-web/src/components/AiAssistPanel.tsx— UI и связка сuseEditorState+callAiAssist;apps/builder-web/src/api/client.ts— реализацияcallAiAssist(прокидываетrequestIdи весьAiAssistRequestна backend);apps/builder-web/src/ai/useAiAssist.ts— (опционально) хук высокого уровня для более сложных сценариев AI‑ассистента.
Важно: UI панели и все пользовательские сообщения — на английском, а архитектурные комментарии и документация — на русском.
5. Валидация DSL и выражений
Валидация в builder-web многоуровневая.
5.1. Структурная валидация DSL (validateAppSchema)
На входе PreviewPanel (и при некоторых действиях редактора) DSL прогоняется через validateAppSchema из @lowcode/dsl.
Проверяется:
- корректность структуры приложения, страниц, компонент;
- уникальность
id; - валидность
appStateиpage.state(типы и значения); - базовая корректность
DataSourcesиEventHandlers; - корректность типов полей
mock.
Ошибка возвращается как список строк, которые в builder-web преобразуются в DslValidationIssue { path, message, severity }.
5.2. Локальная syntax‑валидация выражений (validateExpressionSyntax)
В expressions/validateExpression.ts реализована быстрая проверка синтаксиса для ExpressionValue.expression.
Она используется:
- при вводе выражения в PropertiesPanel/StatePanel;
- в PreviewPanel для прохода по DSL (без AST), чтобы собрать первичные diagnostics.
5.3. Семантическая валидация выражений (validateExpressionsInAstApp)
На уровне AST, через @lowcode/dsl-compiler, выполняется более глубокая проверка:
const ast = buildAstFromDsl(app);
const exprDiags = validateExpressionsInAstApp(app, ast);
Здесь выражения уже видят:
- переменные
state.*из глобального/страничного состояния; props.*изComponentDefinition.props(WIP);data.*изDataSource(включая те, для которых настроены моки), с контрактомdata.<source>.value/error/loading/lastResultAt;- ожидаемые типы пропов.
Результат — список ExpressionDiagnostic с указанием страницы, компонента и пропа.
5.4. Сведение ошибок в issueNodeIds
PreviewPanel объединяет источники ошибок:
DslValidationIssue[](структурная валидация DSL);ExpressionDiagnostic[](выражения по DSL + AST);- сообщения локального
validateExpressionSyntax; - сообщения об ошибках компиляции/исполнения превью (включая ошибки из mock/executor).
Итоговый набор ID передаётся в EditorState через onErrorNodeIdsChange([...ids]) и используется Canvas для подсветки нод с ошибками.
6. Превью и интеграция с runtime-core
6.1. Общий pipeline превью
Текущая версия PreviewPanel (components/PreviewPanel.tsx) работает поверх @lowcode/runtime-core.
Высокоуровневая схема:
graph TD
DSL[AppSchema] --> VALIDATE[validateAppSchema\n+ локальная проверка выражений]
VALIDATE --> AST[buildAstFromDsl\nAstApp]
AST --> EXPR[validateExpressionsInAstApp]
EXPR -->|diagnostics → issueNodeIds| CANVAS[Canvas]
AST --> REACTBNDL[compileDslToReact\n(TSX bundle)]
REACTBNDL --> RUNTIME[@lowcode/runtime-core\ncreateRuntimeInstance]
RUNTIME --> PREVIEW[PreviewPanel\nRuntimeInstance.RootComponent]
Ключевые шаги внутри PreviewPanel:
- Сброс состояния при изменении
app. - Валидация DSL и выражений.
- При ошибках — Canvas подсвечивает ноды, превью не компилируется.
- При отсутствии ошибок — компиляция DSL в TSX‑бандл и создание
RuntimeInstance. - Передача
dataSourcesиdataSourceExecutorс поддержкой моков. - Формирование обёртки вокруг
RootComponentи рендер превью. - Кнопка
Reset runtimeсбрасывает состояние runtime и перерисовывает превью. - Панель
Preview paramsпозволяет задатьroute.paramsдля активной страницы и применяет их черезdispatch({ type: 'navigate', ... }). Query‑параметры (route.query) пока приходят только из runtime‑снапшота и сохраняются при навигации.
6.2. RuntimeStateInspector и лог событий
RuntimeStateInspector и лог событий runtime позволяют наблюдать за:
- изменениями глобального/страничного состояния;
- вызовами
callDataSourceи их результатами (в карточках dataSource видно__lastResultAt,__lastError,__mockScenario); - навигацией по страницам;
- произвольными событиями, помеченными через
RuntimeChangeReason.
Inspector также даёт быстрый контроль сценариев моков: SegmentedControl в карточке источника переключает success/error, сохраняет override (mockScenarioOverride) и позволяет вызвать Call data source вручную, не теряя историю state.
Лог событий используется как devtools‑панель под превью.
7. Подсветка ошибок в Canvas
Canvas подсвечивает компоненты, у которых есть ошибки, при помощи issueNodeIds.
Источник issueNodeIds — PreviewPanel, который агрегирует все ошибки из валидации DSL, выражений и runtime и пытается сопоставить их с конкретными componentId.
8. SPA‑архитектура и страницы
Редактор — Single Page Application на React (Vite + React Router).
Типичный набор маршрутов:
/ # стартовая страница / список проектов или шаблонов
/editor # основной режим редактора
/editor/:id # привязка к конкретному проекту (в будущем)
SPA даёт:
- мгновенные переходы между режимами без перезагрузки страницы;
- единый EditorState на всём жизненном цикле приложения;
- возможность держать в DOM одновременно Canvas, Preview, PropertiesPanel, DataSourcesPanel, AiAssistPanel и devtools.
9. Интеграция с AI‑оркестратором
AI‑интеграция в платформе разделена на три слоя:
- builder-web — UI и выбор моделей (AiAssistPanel /
useAiAssist); @lowcode/api— HTTP‑эндпоинт/ai/assistи адаптер к@lowcode/ai-orchestrator;@lowcode/ai-orchestrator— формирование промпта, строгий JSON‑формат ответа и применение операций к DSL.
9.1. Поток запроса AI‑ассистента
Высокоуровнево цепочка выглядит так:
AiAssistPanel (builder-web)
↓ POST /ai/assist
apps/api (AiController/AiService)
↓ assist(input, callModel)
@lowcode/ai-orchestrator
↓ buildAssistModelRequest
↓ AI‑провайдер (OpenAI / AI Tunnel / local)
↓ parseAssistModelResponse
↓ applyAiOperations(appSchema, operations)
→ updatedAppSchema
Builder-web не знает о конкретном HTTP‑эндпоинте AI‑провайдера и API‑ключах — он работает только с абстрактным AiAssistRequest / AiAssistResponse.
9.2. Где задаётся модель по умолчанию
Ключевой момент архитектуры: дефолтные значения AI‑провайдера и модели принадлежат frontend‑слою (builder-web), а не backend‑API.
-
В хуке
apps/builder-web/src/ai/useAiAssist.tsзаданы значения по умолчанию:const DEFAULT_AI_PROVIDER = (import.meta.env && import.meta.env.VITE_AI_PROVIDER) ?? 'direct';
const DEFAULT_AI_MODEL = (import.meta.env && import.meta.env.VITE_AI_MODEL) ?? 'gpt-4.1-mini'; -
В
AiAssistPanel.tsxпри формировании запроса вcallAiAssistявным образом указываютсяproviderиmodel. В текущей конфигурации это, как правило,provider: 'aiTunnel'иmodel: 'gpt-5-mini'. -
Если в будущем нужно изменить модель по умолчанию, это делается на стороне builder-web:
- либо через Vite‑переменные окружения
VITE_AI_PROVIDERиVITE_AI_MODEL; - либо через правку констант/пропсов в
useAiAssist.tsилиAiAssistPanel.tsx.
- либо через Vite‑переменные окружения
Backend apps/api умеет подставлять свои дефолты (через AI_DEFAULT_PROVIDER / AI_DEFAULT_MODEL), но они рассматриваются как страховочная настройка. Архитектурно предполагается, что конкретный provider/model выбирает UI‑слой, и API получает уже полностью определённый AiAssistRequest.
9.3. Формат промпта и ответа (оркестратор)
Весь контракт с моделью описан в @lowcode/ai-orchestrator:
-
packages/ai-orchestrator/src/assist/buildPrompt.ts— формирует:system‑сообщение с детальным описанием роли ассистента и жёстким JSON‑форматом ответа;user‑сообщение с JSON‑payload, содержащимprompt,appSchema,projectId,pageId,hints.
-
packages/ai-orchestrator/src/assist/parseResponse.ts— парсит ответ модели:- ожидается чистый JSON‑объект без Markdown;
- верхний уровень:
{ "operations": AiOperation[], "explanation": string }; - массив
operationsнормализуется к строгому типуAiOperation.
-
packages/ai-orchestrator/src/assist/applyOperations.ts— применяетAiOperation[]кAppSchemaи возвращаетupdatedAppSchema.
Builder-web видит только итоговый AiAssistResponse:
interface AiAssistResponse {
updatedAppSchema?: AppSchema;
operations?: AiOperation[];
explanation?: string;
error?: AiError;
}
и реагирует на него так:
- если есть
error— показывает текст ошибки в панели AI; - если есть
updatedAppSchema— заменяет текущийappв EditorState; - если есть
explanation— показывает его в блоке Last explanation.
10. Принципы проектирования builder-web
-
DSL — единственный источник истины.
- любой UI‑элемент отражает и редактирует DSL, а не промежуточные структуры;
- превью и AI‑ассистент всегда работают с актуальным
AppSchema.
-
Иммутабельность и прозрачные обновления.
- все изменения делаются через pure‑функции обновления DSL;
- EditorState остаётся предсказуемым, возможен undo/redo.
-
Разделение слоёв: DSL → AST → runtime → AI.
- DSL‑валидация и AST‑анализ живут в
@lowcode/dslи@lowcode/dsl-compiler; - исполнение бандла и состояние — в
@lowcode/runtime-core; - генерация операций AI‑ассистентом — в
@lowcode/ai-orchestrator; - builder-web отвечает за UX и выбор моделей.
- DSL‑валидация и AST‑анализ живут в
-
Ошибки должны быть видимы.
- любая ошибка (DSL, выражения, runtime, моки, AI) подсвечивается в UI;
- Canvas, панели и AiAssistPanel позволяют быстро найти и исправить источник.
-
ExpressionValue — полноценный гражданин DSL.
- выражения валидируются и по синтаксису, и по семантике;
- в будущем — подсветка и интерактивная отладка выражений.
-
UI-kit как единая дизайн‑система.
- LayoutShell, панели, карточки, кнопки и формы построены на
@lowcode/ui-kit; - runtime-host будет использовать тот же UI‑kit.
- LayoutShell, панели, карточки, кнопки и формы построены на
-
Моки — обязательный слой между превью и внешним миром.
- builder-web не делает боевые запросы к API;
- превью опирается только на mock‑данные или статический
config.data; - runtime-host отвечает за реальные HTTP/DB/WebSocket‑вызовы.
-
AI‑ассистент — дополнение к ручному редактированию, а не замена.
- AI генерирует декларативные операции поверх существующего DSL;
- пользователь всегда может донастроить результат через панели;
- формат операций полностью контролируется платформой.
-
Расширяемость и эволюция.
- любые новые функции проходят тот же путь: DSL → AST → Preview → Runtime → AI;
- архитектура допускает появление новых компонентов, действий, dataSources, mock‑режимов, выражений и AI‑операций без ломки ядра.
Builder-web уже сейчас выступает как центральный интерфейс Lowcode‑платформы и основное рабочее место для создания и сопровождения DSL‑приложений — как вручную, так и с помощью AI‑ассистента.