@nocobase/plugin-flow-engine 2.2.0-beta.6 → 2.2.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/externalVersion.js +9 -9
  2. package/dist/locale/en-US.json +1 -0
  3. package/dist/locale/index.d.ts +2 -0
  4. package/dist/locale/zh-CN.json +1 -0
  5. package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
  6. package/dist/node_modules/acorn/package.json +1 -1
  7. package/dist/node_modules/acorn-jsx/package.json +1 -1
  8. package/dist/node_modules/acorn-walk/package.json +1 -1
  9. package/dist/node_modules/ses/package.json +1 -1
  10. package/dist/node_modules/zod/package.json +1 -1
  11. package/dist/server/flow-surfaces/authoring-validation.d.ts +2 -1
  12. package/dist/server/flow-surfaces/authoring-validation.js +43 -18
  13. package/dist/server/flow-surfaces/blueprint/compile-plan.js +19 -15
  14. package/dist/server/flow-surfaces/blueprint/export-document.js +7 -2
  15. package/dist/server/flow-surfaces/blueprint/normalize-document.js +2 -1
  16. package/dist/server/flow-surfaces/blueprint/public-types.d.ts +1 -0
  17. package/dist/server/flow-surfaces/builder.js +24 -4
  18. package/dist/server/flow-surfaces/catalog.js +10 -1
  19. package/dist/server/flow-surfaces/configure-options.js +1 -0
  20. package/dist/server/flow-surfaces/constants.d.ts +2 -2
  21. package/dist/server/flow-surfaces/service.d.ts +11 -0
  22. package/dist/server/flow-surfaces/service.js +265 -31
  23. package/dist/server/flow-surfaces/types.d.ts +2 -1
  24. package/dist/swagger/flow-surfaces.d.ts +13 -0
  25. package/dist/swagger/flow-surfaces.examples.d.ts +3 -0
  26. package/dist/swagger/flow-surfaces.examples.js +3 -0
  27. package/dist/swagger/flow-surfaces.js +19 -6
  28. package/dist/swagger/index.d.ts +13 -0
  29. package/package.json +2 -2
@@ -690,6 +690,7 @@ function assertNoFlowSurfaceIdTitleFieldSettings(value, options = {}) {
690
690
  visit(value, options.basePath || []);
691
691
  }
692
692
  const DEFAULT_ADMIN_UI_LAYOUT_UID = "admin-layout-model";
693
+ const MOBILE_UI_LAYOUT_TYPE = "mobile";
693
694
  class FlowSurfacesService {
694
695
  constructor(plugin) {
695
696
  this.plugin = plugin;
@@ -923,12 +924,12 @@ class FlowSurfacesService {
923
924
  transaction
924
925
  });
925
926
  }
926
- async findMenuGroupRoutesByTitle(title, transaction) {
927
+ async findMenuGroupRoutesByTitle(title, transaction, layoutUid) {
927
928
  const normalizedTitle = String(title || "").trim();
928
929
  if (!normalizedTitle) {
929
930
  return [];
930
931
  }
931
- return import_lodash.default.castArray(
932
+ const routes = import_lodash.default.castArray(
932
933
  await this.db.getRepository("desktopRoutes").find({
933
934
  filter: {
934
935
  type: "group",
@@ -937,6 +938,18 @@ class FlowSurfacesService {
937
938
  transaction
938
939
  })
939
940
  );
941
+ const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
942
+ if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
943
+ return routes;
944
+ }
945
+ const matchedRoutes = [];
946
+ for (const route of routes) {
947
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(this.readRouteField(route, "id"), transaction);
948
+ if (import_lodash.default.intersection(routeLayoutUids, layoutUidFilter).length) {
949
+ matchedRoutes.push(route);
950
+ }
951
+ }
952
+ return matchedRoutes;
940
953
  }
941
954
  routeParentIdMatches(routeParentId, parentId) {
942
955
  if (import_lodash.default.isNil(routeParentId) && import_lodash.default.isNil(parentId)) {
@@ -944,28 +957,42 @@ class FlowSurfacesService {
944
957
  }
945
958
  return String(routeParentId ?? "") === String(parentId ?? "");
946
959
  }
947
- async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction) {
948
- const routes = await this.findMenuGroupRoutesByTitle(title, transaction);
960
+ async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid) {
961
+ const routes = await this.findMenuGroupRoutesByTitle(title, transaction, layoutUid);
949
962
  return routes.filter(
950
963
  (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
951
964
  );
952
965
  }
953
- async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction) {
966
+ async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid) {
954
967
  const normalizedTitle = String(title || "").trim();
955
- const normalizedParentId = String(parentId ?? "").trim();
956
- if (!normalizedTitle || !normalizedParentId) {
968
+ if (!normalizedTitle) {
957
969
  return [];
958
970
  }
959
- return import_lodash.default.castArray(
971
+ const routes = import_lodash.default.castArray(
960
972
  await this.db.getRepository("desktopRoutes").find({
961
- filter: {
973
+ filter: (0, import_service_utils.buildDefinedPayload)({
962
974
  type: "flowPage",
963
975
  title: normalizedTitle,
964
- parentId: normalizedParentId
965
- },
976
+ parentId: import_lodash.default.isNil(parentId) ? void 0 : String(parentId)
977
+ }),
966
978
  transaction
967
979
  })
968
980
  );
981
+ const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
982
+ const layoutMatchedRoutes = [];
983
+ if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
984
+ layoutMatchedRoutes.push(...routes);
985
+ } else {
986
+ for (const route of routes) {
987
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(this.readRouteField(route, "id"), transaction);
988
+ if (import_lodash.default.intersection(routeLayoutUids, layoutUidFilter).length) {
989
+ layoutMatchedRoutes.push(route);
990
+ }
991
+ }
992
+ }
993
+ return layoutMatchedRoutes.filter(
994
+ (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
995
+ );
969
996
  }
970
997
  async assertMenuParentIsGroup(parentMenuRouteId, transaction) {
971
998
  if (import_lodash.default.isNil(parentMenuRouteId)) {
@@ -1071,6 +1098,29 @@ class FlowSurfacesService {
1071
1098
  });
1072
1099
  return defaultLayout ? [DEFAULT_ADMIN_UI_LAYOUT_UID] : [];
1073
1100
  }
1101
+ async findDesktopRouteUiLayoutByUid(layoutUid, transaction) {
1102
+ const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
1103
+ if (!normalizedLayoutUid || !this.hasDesktopRouteUiLayoutsRelation()) {
1104
+ return null;
1105
+ }
1106
+ return this.db.getRepository("uiLayouts").findOne({
1107
+ filter: {
1108
+ uid: normalizedLayoutUid
1109
+ },
1110
+ transaction
1111
+ });
1112
+ }
1113
+ async getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction) {
1114
+ var _a;
1115
+ const uiLayout = await this.findDesktopRouteUiLayoutByUid(layoutUid, transaction);
1116
+ return String(((_a = uiLayout == null ? void 0 : uiLayout.get) == null ? void 0 : _a.call(uiLayout, "layoutType")) || (uiLayout == null ? void 0 : uiLayout.layoutType) || "").trim() || void 0;
1117
+ }
1118
+ async isMobileDesktopRouteUiLayoutUid(layoutUid, transaction) {
1119
+ if (!layoutUid) {
1120
+ return false;
1121
+ }
1122
+ return await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction) === MOBILE_UI_LAYOUT_TYPE;
1123
+ }
1074
1124
  async resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction) {
1075
1125
  const parentRouteId = this.readRouteField(parentRoute, "id");
1076
1126
  if (!import_lodash.default.isNil(parentRouteId)) {
@@ -1081,6 +1131,86 @@ class FlowSurfacesService {
1081
1131
  }
1082
1132
  return this.resolveDefaultDesktopRouteUiLayoutUids(transaction);
1083
1133
  }
1134
+ normalizeExplicitDesktopRouteLayoutUid(value) {
1135
+ const normalized = String(value || "").trim();
1136
+ return normalized || void 0;
1137
+ }
1138
+ normalizeDesktopRouteLayoutUidFilter(value) {
1139
+ return import_lodash.default.uniq(
1140
+ import_lodash.default.castArray(value).map((item) => this.normalizeExplicitDesktopRouteLayoutUid(item)).filter(Boolean)
1141
+ );
1142
+ }
1143
+ async resolveDesktopRouteLayoutUidFilter(value, transaction) {
1144
+ const explicitLayoutUids = this.normalizeDesktopRouteLayoutUidFilter(value);
1145
+ if (explicitLayoutUids.length) {
1146
+ return explicitLayoutUids;
1147
+ }
1148
+ return this.resolveDefaultDesktopRouteUiLayoutUids(transaction);
1149
+ }
1150
+ async assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, path, transaction) {
1151
+ if (!this.hasDesktopRouteUiLayoutsRelation()) {
1152
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} requires plugin-ui-layout desktop route relation support`, {
1153
+ ruleId: "navigation-layout-unsupported",
1154
+ path,
1155
+ details: {
1156
+ layoutUid
1157
+ }
1158
+ });
1159
+ }
1160
+ const uiLayout = await this.db.getRepository("uiLayouts").findOne({
1161
+ filter: {
1162
+ uid: layoutUid,
1163
+ enabled: true
1164
+ },
1165
+ transaction
1166
+ });
1167
+ if (!uiLayout) {
1168
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} must reference an enabled ui layout`, {
1169
+ ruleId: "navigation-layout-not-found",
1170
+ path,
1171
+ details: {
1172
+ layoutUid
1173
+ }
1174
+ });
1175
+ }
1176
+ }
1177
+ async assertDesktopRouteBelongsToUiLayout(actionName, route, layoutUid, path, transaction) {
1178
+ const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
1179
+ if (!normalizedLayoutUid || !route) {
1180
+ return;
1181
+ }
1182
+ const routeId = this.readRouteField(route, "id");
1183
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(routeId, transaction);
1184
+ if (routeLayoutUids.includes(normalizedLayoutUid)) {
1185
+ return;
1186
+ }
1187
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} does not belong to ui layout '${normalizedLayoutUid}'`, {
1188
+ ruleId: "navigation-route-layout-mismatch",
1189
+ path,
1190
+ details: {
1191
+ routeId,
1192
+ layoutUid: normalizedLayoutUid,
1193
+ routeLayoutUids
1194
+ }
1195
+ });
1196
+ }
1197
+ async resolveRequestedDesktopRouteUiLayoutUids(values, parentRoute, actionName, transaction) {
1198
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
1199
+ if (layoutUid) {
1200
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", transaction);
1201
+ if (parentRoute) {
1202
+ await this.assertDesktopRouteBelongsToUiLayout(
1203
+ actionName,
1204
+ parentRoute,
1205
+ layoutUid,
1206
+ "values.parentMenuRouteId",
1207
+ transaction
1208
+ );
1209
+ }
1210
+ return [layoutUid];
1211
+ }
1212
+ return this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction);
1213
+ }
1084
1214
  async setDesktopRouteUiLayouts(routeId, uiLayoutUids, transaction) {
1085
1215
  const relationRouteId = this.normalizeDesktopRouteRelationId(routeId);
1086
1216
  if (import_lodash.default.isUndefined(relationRouteId) || !uiLayoutUids.length || !this.hasDesktopRouteUiLayoutsRelation()) {
@@ -1181,8 +1311,19 @@ class FlowSurfacesService {
1181
1311
  async createFlowMenuGroup(values, transaction) {
1182
1312
  const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1183
1313
  const parentId = this.readRouteField(parentRoute, "id") ?? null;
1314
+ const routeUiLayoutUids = await this.resolveRequestedDesktopRouteUiLayoutUids(
1315
+ values,
1316
+ parentRoute,
1317
+ "createMenu",
1318
+ transaction
1319
+ );
1184
1320
  const title = String(values.title || "").trim();
1185
- const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction);
1321
+ const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(
1322
+ parentId,
1323
+ title,
1324
+ transaction,
1325
+ routeUiLayoutUids
1326
+ );
1186
1327
  if (existingGroups.length === 1) {
1187
1328
  return this.buildMenuResult(existingGroups[0]);
1188
1329
  }
@@ -1222,7 +1363,7 @@ class FlowSurfacesService {
1222
1363
  });
1223
1364
  await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1224
1365
  this.readRouteField(route, "id"),
1225
- await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction),
1366
+ routeUiLayoutUids,
1226
1367
  transaction
1227
1368
  );
1228
1369
  return this.buildMenuResult(route);
@@ -1230,6 +1371,12 @@ class FlowSurfacesService {
1230
1371
  async createFlowMenuItem(values, transaction) {
1231
1372
  var _a;
1232
1373
  const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1374
+ const routeUiLayoutUids = await this.resolveRequestedDesktopRouteUiLayoutUids(
1375
+ values,
1376
+ parentRoute,
1377
+ "createMenu",
1378
+ transaction
1379
+ );
1233
1380
  this.assertVisibleNavigationIcon("createMenu", "values", values);
1234
1381
  const pageSchemaUid = values.pageSchemaUid || (0, import_utils.uid)();
1235
1382
  const menuSchemaUid = (0, import_utils.uid)();
@@ -1276,7 +1423,7 @@ class FlowSurfacesService {
1276
1423
  });
1277
1424
  await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1278
1425
  this.readRouteField(createdRoute, "id"),
1279
- await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction),
1426
+ routeUiLayoutUids,
1280
1427
  transaction
1281
1428
  );
1282
1429
  const route = await desktopRoutes.findOne({
@@ -1313,7 +1460,7 @@ class FlowSurfacesService {
1313
1460
  });
1314
1461
  }
1315
1462
  sanitizePublicCreateMenuValues(values) {
1316
- return import_lodash.default.pick(values, ["title", "type", "icon", "tooltip", "hideInMenu", "parentMenuRouteId"]);
1463
+ return import_lodash.default.pick(values, ["title", "type", "layoutUid", "icon", "tooltip", "hideInMenu", "parentMenuRouteId"]);
1317
1464
  }
1318
1465
  async loadRouteBackedPageStructure(route, transaction) {
1319
1466
  const routeId = this.readRouteField(route, "id");
@@ -3223,7 +3370,13 @@ class FlowSurfacesService {
3223
3370
  if (!groupTitle) {
3224
3371
  return document;
3225
3372
  }
3226
- const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(null, groupTitle, transaction);
3373
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(document.navigation.layoutUid);
3374
+ const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(
3375
+ null,
3376
+ groupTitle,
3377
+ transaction,
3378
+ layoutUid || await this.resolveDefaultDesktopRouteUiLayoutUids(transaction)
3379
+ );
3227
3380
  if (!matchedRoutes.length) {
3228
3381
  return document;
3229
3382
  }
@@ -3248,29 +3401,89 @@ class FlowSurfacesService {
3248
3401
  }
3249
3402
  };
3250
3403
  }
3404
+ async normalizeApplyBlueprintCreateMobileNavigation(document, transaction) {
3405
+ var _a;
3406
+ if (document.mode !== "create") {
3407
+ return document;
3408
+ }
3409
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3410
+ if (!await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3411
+ return document;
3412
+ }
3413
+ return {
3414
+ ...document,
3415
+ navigation: (0, import_service_utils.buildDefinedPayload)({
3416
+ ...document.navigation,
3417
+ layoutUid,
3418
+ group: void 0
3419
+ })
3420
+ };
3421
+ }
3422
+ async assertApplyBlueprintCreateNavigationLayout(document, transaction) {
3423
+ var _a, _b, _c;
3424
+ if (document.mode !== "create") {
3425
+ return;
3426
+ }
3427
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3428
+ if (!layoutUid) {
3429
+ return;
3430
+ }
3431
+ await this.assertEnabledDesktopRouteUiLayoutUid(
3432
+ layoutUid,
3433
+ "applyBlueprint",
3434
+ "values.navigation.layoutUid",
3435
+ transaction
3436
+ );
3437
+ if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3438
+ return;
3439
+ }
3440
+ const groupRouteId = (_c = (_b = document.navigation) == null ? void 0 : _b.group) == null ? void 0 : _c.routeId;
3441
+ if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "") {
3442
+ return;
3443
+ }
3444
+ const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, transaction);
3445
+ await this.assertDesktopRouteBelongsToUiLayout(
3446
+ "applyBlueprint",
3447
+ groupRoute,
3448
+ layoutUid,
3449
+ "values.navigation.group.routeId",
3450
+ transaction
3451
+ );
3452
+ }
3251
3453
  async resolveApplyBlueprintCreatePageIdentity(document, transaction) {
3252
- var _a, _b, _c, _d, _e;
3454
+ var _a, _b, _c, _d, _e, _f;
3253
3455
  if (document.mode !== "create") {
3254
3456
  return document;
3255
3457
  }
3256
3458
  const groupRouteId = (_b = (_a = document.navigation) == null ? void 0 : _a.group) == null ? void 0 : _b.routeId;
3257
- const pageTitle = String(((_c = document.page) == null ? void 0 : _c.title) || ((_e = (_d = document.navigation) == null ? void 0 : _d.item) == null ? void 0 : _e.title) || "").trim();
3258
- if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "" || !pageTitle) {
3459
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_c = document.navigation) == null ? void 0 : _c.layoutUid);
3460
+ const pageTitle = String(((_d = document.page) == null ? void 0 : _d.title) || ((_f = (_e = document.navigation) == null ? void 0 : _e.item) == null ? void 0 : _f.title) || "").trim();
3461
+ const hasGroupRouteId = !import_lodash.default.isNil(groupRouteId) && groupRouteId !== "";
3462
+ if (!pageTitle || !hasGroupRouteId && !layoutUid) {
3259
3463
  return document;
3260
3464
  }
3261
- const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(groupRouteId, pageTitle, transaction);
3465
+ const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, transaction) : null;
3466
+ const layoutUidFilter = layoutUid || (groupRoute ? await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, transaction) : void 0);
3467
+ const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(
3468
+ hasGroupRouteId ? groupRouteId : null,
3469
+ pageTitle,
3470
+ transaction,
3471
+ layoutUidFilter
3472
+ );
3262
3473
  if (!matchedPages.length) {
3263
3474
  return document;
3264
3475
  }
3265
3476
  if (matchedPages.length > 1) {
3477
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3266
3478
  (0, import_errors.throwBadRequest)(
3267
- `flowSurfaces applyBlueprint navigation.group.routeId '${groupRouteId}' already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3479
+ `flowSurfaces applyBlueprint ${locationLabel} already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3268
3480
  );
3269
3481
  }
3270
3482
  const pageSchemaUid = String(this.readRouteField(matchedPages[0], "schemaUid") || "").trim();
3271
3483
  if (!pageSchemaUid) {
3484
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3272
3485
  (0, import_errors.throwBadRequest)(
3273
- `flowSurfaces applyBlueprint existing flow page '${pageTitle}' under navigation.group.routeId '${groupRouteId}' is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3486
+ `flowSurfaces applyBlueprint existing flow page '${pageTitle}' ${locationLabel} is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3274
3487
  );
3275
3488
  }
3276
3489
  return {
@@ -3285,7 +3498,15 @@ class FlowSurfacesService {
3285
3498
  async prepareApplyBlueprintRequest(values, transaction, createdKanbanSortFields) {
3286
3499
  var _a;
3287
3500
  const initialDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
3288
- const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(initialDocument, transaction);
3501
+ await this.assertApplyBlueprintCreateNavigationLayout(initialDocument, transaction);
3502
+ const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(
3503
+ initialDocument,
3504
+ transaction
3505
+ );
3506
+ const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(
3507
+ mobileNormalizedDocument,
3508
+ transaction
3509
+ );
3289
3510
  const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, transaction);
3290
3511
  await this.prepareApplyBlueprintKanbanBlocks(document, transaction, createdKanbanSortFields);
3291
3512
  const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, transaction) : void 0;
@@ -3942,7 +4163,8 @@ class FlowSurfacesService {
3942
4163
  await (0, import_authoring_validation.assertFlowSurfaceAuthoringPayload)("applyBlueprint", values, {
3943
4164
  transaction: options.transaction,
3944
4165
  enabledPackages,
3945
- findMenuGroupRoutesByTitle: (title, transaction) => this.findMenuGroupRoutesByTitle(title, transaction),
4166
+ findMenuGroupRoutesByTitle: (title, transaction, layoutUid) => this.findMenuGroupRoutesByTitle(title, transaction, layoutUid),
4167
+ getUiLayoutTypeByUid: (layoutUid, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction),
3946
4168
  getCollection: (dataSourceKey, collectionName) => this.getCollection(dataSourceKey || "main", collectionName || "")
3947
4169
  });
3948
4170
  }
@@ -6026,6 +6248,11 @@ class FlowSurfacesService {
6026
6248
  const pageSchemaUid = this.readRouteField(route, "schemaUid");
6027
6249
  const structure = await this.loadRouteBackedPageStructure(route, transaction);
6028
6250
  let tabRoute = structure.tabRoutes[0];
6251
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
6252
+ if (layoutUid) {
6253
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, "createPage", "values.layoutUid", transaction);
6254
+ await this.assertDesktopRouteBelongsToUiLayout("createPage", route, layoutUid, "values.menuRouteId", transaction);
6255
+ }
6029
6256
  const routeUiLayoutUids = await this.ensureDesktopRouteUiLayouts(route, transaction);
6030
6257
  const routeOptions = this.readRouteOptions(route);
6031
6258
  if (!routeOptions[FLOW_SURFACE_MENU_BINDABLE_OPTION_KEY]) {
@@ -17244,13 +17471,20 @@ class FlowSurfacesService {
17244
17471
  description: changes.description,
17245
17472
  className: changes.className
17246
17473
  }),
17247
- stepParams: (0, import_service_utils.hasDefinedValue)(changes, ["code", "version"]) ? {
17248
- jsSettings: {
17249
- runJs: (0, import_service_utils.buildDefinedPayload)({
17250
- code: changes.code,
17251
- version: changes.version
17252
- })
17253
- }
17474
+ stepParams: (0, import_service_utils.hasDefinedValue)(changes, ["code", "version", "showBlockCard"]) ? {
17475
+ jsSettings: (0, import_service_utils.buildDefinedPayload)({
17476
+ ...(0, import_service_utils.hasDefinedValue)(changes, ["code", "version"]) ? {
17477
+ runJs: (0, import_service_utils.buildDefinedPayload)({
17478
+ code: changes.code,
17479
+ version: changes.version
17480
+ })
17481
+ } : {},
17482
+ ...(0, import_service_utils.hasOwnDefined)(changes, "showBlockCard") ? {
17483
+ showBlockCard: {
17484
+ showBlockCard: changes.showBlockCard
17485
+ }
17486
+ } : {}
17487
+ })
17254
17488
  } : void 0
17255
17489
  },
17256
17490
  options
@@ -45,7 +45,8 @@ export type FlowSurfaceConfigureOption = {
45
45
  type: FlowSurfaceConfigureOptionValueType;
46
46
  description?: string;
47
47
  enum?: Array<string | number | boolean>;
48
- example?: any;
48
+ example?: unknown;
49
+ default?: unknown;
49
50
  supportsFlowContext?: boolean;
50
51
  };
51
52
  export type FlowSurfaceConfigureOptions = Record<string, FlowSurfaceConfigureOption>;
@@ -2635,6 +2635,10 @@ declare const _default: {
2635
2635
  FlowSurfaceApplyBlueprintNavigation: {
2636
2636
  type: string;
2637
2637
  properties: {
2638
+ layoutUid: {
2639
+ type: string;
2640
+ description: string;
2641
+ };
2638
2642
  group: {
2639
2643
  $ref: string;
2640
2644
  };
@@ -3148,6 +3152,10 @@ declare const _default: {
3148
3152
  type: string;
3149
3153
  required: string[];
3150
3154
  properties: {
3155
+ layoutUid: {
3156
+ type: string;
3157
+ description: string;
3158
+ };
3151
3159
  title: {
3152
3160
  type: string;
3153
3161
  };
@@ -3270,6 +3278,10 @@ declare const _default: {
3270
3278
  FlowSurfaceCreatePageRequest: {
3271
3279
  type: string;
3272
3280
  properties: {
3281
+ layoutUid: {
3282
+ type: string;
3283
+ description: string;
3284
+ };
3273
3285
  menuRouteId: {
3274
3286
  oneOf: {
3275
3287
  type: string;
@@ -6102,6 +6114,7 @@ declare const _default: {
6102
6114
  };
6103
6115
  };
6104
6116
  example: {};
6117
+ default: {};
6105
6118
  supportsFlowContext: {
6106
6119
  type: string;
6107
6120
  };
@@ -668,6 +668,7 @@ export declare const flowSurfaceExamples: {
668
668
  title: string;
669
669
  description: string;
670
670
  className: string;
671
+ showBlockCard: boolean;
671
672
  version: string;
672
673
  code: string;
673
674
  };
@@ -764,6 +765,7 @@ export declare const flowSurfaceExamples: {
764
765
  title: string;
765
766
  description: string;
766
767
  className: string;
768
+ showBlockCard: boolean;
767
769
  version: string;
768
770
  code: string;
769
771
  };
@@ -1068,6 +1070,7 @@ export declare const flowSurfaceExamples: {
1068
1070
  settings: {
1069
1071
  title: string;
1070
1072
  description: string;
1073
+ showBlockCard: boolean;
1071
1074
  version: string;
1072
1075
  code: string;
1073
1076
  };
@@ -893,6 +893,7 @@ const flowSurfaceExamples = {
893
893
  title: "Custom hero",
894
894
  description: "Rendered by JS block runtime",
895
895
  className: "hero-shell",
896
+ showBlockCard: true,
896
897
  version: "1.0.0",
897
898
  code: "ctx.render('<div>Hello from JS block</div>');"
898
899
  }
@@ -996,6 +997,7 @@ const flowSurfaceExamples = {
996
997
  title: "Users hero",
997
998
  description: "Rendered from FlowSurfaces configure",
998
999
  className: "users-hero",
1000
+ showBlockCard: true,
999
1001
  version: "1.0.1",
1000
1002
  code: "ctx.render('<div>Users hero</div>');"
1001
1003
  }
@@ -1341,6 +1343,7 @@ const flowSurfaceExamples = {
1341
1343
  settings: {
1342
1344
  title: "Users banner",
1343
1345
  description: "Custom JS rendered banner",
1346
+ showBlockCard: true,
1344
1347
  version: "1.0.0",
1345
1348
  code: "ctx.render('<div>Users banner</div>');"
1346
1349
  }
@@ -623,7 +623,7 @@ const actionDocs = {
623
623
  tags: [FLOW_SURFACES_TAG],
624
624
  summary: "Apply a page blueprint to create or replace one Modern page",
625
625
  description: valuesCompatibilityNote(
626
- `Accepts one simplified JSON page blueprint and compiles it to internal flow-surface operations. The public blueprint describes page structure (\`create\` or \`replace\`, page metadata, ordered tabs, blocks, fields, actions, inline popups, optional reusable assets) and optional top-level \`reaction.items[]\` for whole-page interaction authoring. Each reaction item targets an explicit local key / bind key produced by the same blueprint run. Only explicitly listed reaction items are written. \`rules: []\` clears the targeted slot. Repeating the same \`(type, target)\` reaction slot in one blueprint is invalid. In \`replace\`, reaction targets always bind to the newly produced blueprint result, not historical nodes from the previous page version; if a slot must exist in the resulting surface, include it explicitly instead of relying on omission. Localized reaction edits on an existing surface should use \`getReactionMeta\` + \`set*Rules\` instead of applying a whole page blueprint again. The request body is that page-document JSON object itself and must not be JSON-stringified. Wrong: \`{ "requestBody": "{\\"version\\":\\"1\\"}" }\`. Internal planning details stay hidden. In \`create\`, \`navigation.group.routeId\` has the highest priority when targeting an existing menu group. If \`routeId\` is present, applyBlueprint ignores \`title\`, \`icon\`, \`tooltip\`, and \`hideInMenu\` on \`navigation.group\`; applyBlueprint create mode does not mutate existing group metadata, so callers should use \`updateMenu\` separately when that is required. When \`routeId\` is omitted and \`navigation.group.title\` is provided, applyBlueprint reuses one existing same-title group when it is unique, creates a new group when none exists, and rejects ambiguous multi-match cases. Metadata such as \`icon\`, \`tooltip\`, and \`hideInMenu\` is used only when a new group is created and is ignored when an existing group is reused. \`replace\` uses \`target.pageSchemaUid\`, updates only the explicit page-level fields provided in \`page\`, maps blueprint tabs to existing route-backed tab slots by index, rewrites each slot in order, removes trailing old tabs, and appends extra new tabs when needed. Tab and block keys are optional in the public blueprint; omit them unless custom layout or cross-block targeting needs a stable in-document identifier. \`layout\` is only allowed on tabs and inline popup documents; blocks themselves do not accept a \`layout\` property. Public applyBlueprint blocks do not support generic \`form\`; use \`editForm\` or \`createForm\`. AI employee actions use \`type: "aiEmployee"\` plus public \`settings.username\`, \`workContext\`, \`tasks\`, \`auto\`, and \`style\`; work context may target \`self\` or a same-blueprint block key and is persisted as real Flow Model \`uid\` values. For JS blocks/fields/actions, \`script\` is a non-empty string asset key into \`assets.scripts\`; put inline JS in \`settings.code\` and \`settings.version\`. Direct \`table\` / \`list\` / \`gridCard\` / \`calendar\` / \`kanban\` blocks may omit \`defaultFilter\`; the backend generates one from live metadata with up to 4 scalar/filterable fields. Explicit values must contain at least the smaller of 3 and the collection eligible-field count, and values with more than 4 fields are truncated before persistence. A valid explicit or generated block-level value backfills the default \`filter\` action \`settings.defaultFilter\`; explicit filter-action \`settings.defaultFilter\` still wins. ${TREE_TABLE_RECORD_ACTION_DEFAULTS_NOTE} ${APPLY_BLUEPRINT_TREE_TABLE_TITLE_FIELD_NOTE} Inline popup documents may set \`popup.tryTemplate=true\` to ask the backend for the best compatible popup template before falling back to local popup content. Inline popup documents may also combine \`popup.tryTemplate\` with \`popup.saveAsTemplate={ name, description, local? }\`: a hit binds the matched template immediately and lets later inline popups in the same blueprint reuse that final bound template through \`popup.template={ local, mode }\`, while a miss requires explicit local \`popup.blocks\` so the fallback popup can be saved and reused. Custom \`edit\` popups that provide \`popup.blocks\` must include exactly one \`editForm\` block; that \`editForm\` may omit \`resource\` and then inherits the opener's current-record context. When layout is omitted, applyBlueprint auto-generates a simple top-to-bottom layout. When a \`replace\` run expands a page to multiple tabs while the current page still has \`enableTabs=false\`, callers must set \`page.enableTabs=true\` explicitly. The response hides execution internals and returns only the resolved page target and final surface readback.`
626
+ `Accepts one simplified JSON page blueprint and compiles it to internal flow-surface operations. The public blueprint describes page structure (\`create\` or \`replace\`, page metadata, ordered tabs, blocks, fields, actions, inline popups, optional reusable assets) and optional top-level \`reaction.items[]\` for whole-page interaction authoring. Each reaction item targets an explicit local key / bind key produced by the same blueprint run. Only explicitly listed reaction items are written. \`rules: []\` clears the targeted slot. Repeating the same \`(type, target)\` reaction slot in one blueprint is invalid. In \`replace\`, reaction targets always bind to the newly produced blueprint result, not historical nodes from the previous page version; if a slot must exist in the resulting surface, include it explicitly instead of relying on omission. Localized reaction edits on an existing surface should use \`getReactionMeta\` + \`set*Rules\` instead of applying a whole page blueprint again. The request body is that page-document JSON object itself and must not be JSON-stringified. Wrong: \`{ "requestBody": "{\\"version\\":\\"1\\"}" }\`. Internal planning details stay hidden. In \`create\`, \`navigation.layoutUid\` optionally scopes menu group reuse, duplicate page identity checks, and newly created routes to an enabled UI layout such as \`admin-layout-model\` or \`mobile-layout-model\`; when the target layout type is mobile, applyBlueprint ignores \`navigation.group\` and creates a root-level mobile tab page. When \`layoutUid\` is omitted, routes keep the existing admin/default inheritance behavior. In non-mobile \`create\`, \`navigation.group.routeId\` has the highest priority when targeting an existing menu group. If \`routeId\` is present, applyBlueprint ignores \`title\`, \`icon\`, \`tooltip\`, and \`hideInMenu\` on \`navigation.group\`; applyBlueprint create mode does not mutate existing group metadata, so callers should use \`updateMenu\` separately when that is required. When \`routeId\` is omitted and \`navigation.group.title\` is provided, applyBlueprint reuses one existing same-title group in the target layout when it is unique, creates a new group when none exists, and rejects ambiguous multi-match cases. Metadata such as \`icon\`, \`tooltip\`, and \`hideInMenu\` is used only when a new group is created and is ignored when an existing group is reused. \`replace\` uses \`target.pageSchemaUid\`, updates only the explicit page-level fields provided in \`page\`, maps blueprint tabs to existing route-backed tab slots by index, rewrites each slot in order, removes trailing old tabs, and appends extra new tabs when needed. Tab and block keys are optional in the public blueprint; omit them unless custom layout or cross-block targeting needs a stable in-document identifier. \`layout\` is only allowed on tabs and inline popup documents; blocks themselves do not accept a \`layout\` property. Public applyBlueprint blocks do not support generic \`form\`; use \`editForm\` or \`createForm\`. AI employee actions use \`type: "aiEmployee"\` plus public \`settings.username\`, \`workContext\`, \`tasks\`, \`auto\`, and \`style\`; work context may target \`self\` or a same-blueprint block key and is persisted as real Flow Model \`uid\` values. For JS blocks/fields/actions, \`script\` is a non-empty string asset key into \`assets.scripts\`; put inline JS in \`settings.code\` and \`settings.version\`. Direct \`table\` / \`list\` / \`gridCard\` / \`calendar\` / \`kanban\` blocks may omit \`defaultFilter\`; the backend generates one from live metadata with up to 4 scalar/filterable fields. Explicit values must contain at least the smaller of 3 and the collection eligible-field count, and values with more than 4 fields are truncated before persistence. A valid explicit or generated block-level value backfills the default \`filter\` action \`settings.defaultFilter\`; explicit filter-action \`settings.defaultFilter\` still wins. ${TREE_TABLE_RECORD_ACTION_DEFAULTS_NOTE} ${APPLY_BLUEPRINT_TREE_TABLE_TITLE_FIELD_NOTE} Inline popup documents may set \`popup.tryTemplate=true\` to ask the backend for the best compatible popup template before falling back to local popup content. Inline popup documents may also combine \`popup.tryTemplate\` with \`popup.saveAsTemplate={ name, description, local? }\`: a hit binds the matched template immediately and lets later inline popups in the same blueprint reuse that final bound template through \`popup.template={ local, mode }\`, while a miss requires explicit local \`popup.blocks\` so the fallback popup can be saved and reused. Custom \`edit\` popups that provide \`popup.blocks\` must include exactly one \`editForm\` block; that \`editForm\` may omit \`resource\` and then inherits the opener's current-record context. When layout is omitted, applyBlueprint auto-generates a simple top-to-bottom layout. When a \`replace\` run expands a page to multiple tabs while the current page still has \`enableTabs=false\`, callers must set \`page.enableTabs=true\` explicitly. The response hides execution internals and returns only the resolved page target and final surface readback.`
627
627
  ),
628
628
  requestBody: {
629
629
  required: true,
@@ -809,7 +809,7 @@ const actionDocs = {
809
809
  tags: [FLOW_SURFACES_TAG],
810
810
  summary: "Create a group menu or a bindable V2 menu item",
811
811
  description: valuesCompatibilityNote(
812
- 'Creates a FlowSurfaces menu node. `type="group"` creates a menu group. `type="item"` creates a menu item that can be bound to a modern page (v2), and automatically fills in the flowPage route, the default hidden tab route, and the RootPageModel anchor.'
812
+ 'Creates a FlowSurfaces menu node. `type="group"` creates a menu group. `type="item"` creates a menu item that can be bound to a modern page (v2), and automatically fills in the flowPage route, the default hidden tab route, and the RootPageModel anchor. Optional `layoutUid` scopes the route to an enabled UI layout; omitted values keep the existing parent/default admin layout inheritance.'
813
813
  ),
814
814
  requestBody: requestBody("FlowSurfaceCreateMenuRequest", import_flow_surfaces.flowSurfaceExamples.createMenu),
815
815
  responses: responses("FlowSurfaceCreateMenuResult")
@@ -827,7 +827,7 @@ const actionDocs = {
827
827
  tags: [FLOW_SURFACES_TAG],
828
828
  summary: "Initialize a modern page for an existing bindable menu item",
829
829
  description: valuesCompatibilityNote(
830
- "Initializes a modern page (v2) for an existing bindable menu item through `menuRouteId` first, and fills in the default BlockGridModel. In compatibility mode, if `menuRouteId` is omitted, the old behavior still applies and a top-level menu plus page will be created automatically. Before initialization, do not call page/tab lifecycle actions such as `addTab`, `updateTab`, `moveTab`, `removeTab`, or `destroyPage`."
830
+ "Initializes a modern page (v2) for an existing bindable menu item through `menuRouteId` first, and fills in the default BlockGridModel. Optional `layoutUid` asserts that the existing menu route belongs to the target UI layout; omitted values keep the existing parent/default admin layout inheritance. In compatibility mode, if `menuRouteId` is omitted, the old behavior still applies and a top-level menu plus page will be created automatically. Before initialization, do not call page/tab lifecycle actions such as `addTab`, `updateTab`, `moveTab`, `removeTab`, or `destroyPage`."
831
831
  ),
832
832
  requestBody: requestBody("FlowSurfaceCreatePageRequest", import_flow_surfaces.flowSurfaceExamples.createPage),
833
833
  responses: responses("FlowSurfaceCreatePageResult")
@@ -1642,6 +1642,7 @@ const schemas = {
1642
1642
  }
1643
1643
  },
1644
1644
  example: {},
1645
+ default: {},
1645
1646
  supportsFlowContext: {
1646
1647
  type: "boolean"
1647
1648
  }
@@ -4166,11 +4167,11 @@ const schemas = {
4166
4167
  properties: {
4167
4168
  routeId: {
4168
4169
  ...STRING_OR_INTEGER_SCHEMA,
4169
- description: "Preferred existing menu-group route id. When present, routeId has the highest priority and title/icon/tooltip/hideInMenu are ignored. applyBlueprint create mode does not mutate existing group metadata; use low-level updateMenu separately when needed."
4170
+ description: "Preferred existing menu-group route id for non-mobile create mode. When present, routeId has the highest priority and title/icon/tooltip/hideInMenu are ignored. Ignored when navigation.layoutUid targets a mobile layout because mobile pages are created as root tabs. applyBlueprint create mode does not mutate existing group metadata; use low-level updateMenu separately when needed."
4170
4171
  },
4171
4172
  title: {
4172
4173
  type: "string",
4173
- description: "Group title for create mode. When `routeId` is omitted, applyBlueprint reuses a same-title group if the match is unique, creates one when no group exists, and rejects ambiguous multi-match cases. If an existing group is reused, group metadata is ignored; use low-level updateMenu to change existing group metadata."
4174
+ description: "Group title for non-mobile create mode. When `routeId` is omitted, applyBlueprint reuses a same-title group if the match is unique, creates one when no group exists, and rejects ambiguous multi-match cases. Ignored when navigation.layoutUid targets a mobile layout because mobile pages are created as root tabs. If an existing group is reused, group metadata is ignored; use low-level updateMenu to change existing group metadata."
4174
4175
  },
4175
4176
  icon: {
4176
4177
  type: "string",
@@ -4190,6 +4191,10 @@ const schemas = {
4190
4191
  FlowSurfaceApplyBlueprintNavigation: {
4191
4192
  type: "object",
4192
4193
  properties: {
4194
+ layoutUid: {
4195
+ type: "string",
4196
+ description: "Optional enabled UI layout uid that scopes create-mode route lookup and newly created routes. Use `mobile-layout-model` for mobile pages; mobile layouts ignore navigation.group and create a root-level tab page. When omitted, the server preserves the existing parent/default admin layout behavior."
4197
+ },
4193
4198
  group: ref("FlowSurfaceApplyBlueprintNavigationGroup"),
4194
4199
  item: {
4195
4200
  type: "object",
@@ -4405,7 +4410,7 @@ const schemas = {
4405
4410
  FlowSurfaceApplyBlueprintRequest: {
4406
4411
  type: "object",
4407
4412
  required: ["mode", "tabs"],
4408
- description: `Simplified page-structure request object for applyBlueprint. \`version\` may be omitted and defaults to '1'. Runtime validation enforces mode-specific rules: create does not accept target, while replace requires target.pageSchemaUid and does not use navigation. For JS blocks/fields/actions, \`script\` is a non-empty string asset key into \`assets.scripts\`, and referenced script assets must provide non-empty \`code\`; put inline JS in \`settings.code\` and \`settings.version\`. ${TREE_TABLE_RECORD_ACTION_DEFAULTS_NOTE} ${APPLY_BLUEPRINT_TREE_TABLE_TITLE_FIELD_NOTE} \`defaults.collections\` may provide main data-source collection-level fieldGroups, popup metadata with required \`name\` and \`description\`, and formBehavior for generated default add/edit popup forms; use \`defaults.dataSources.<dataSourceKey>.collections\` for external data sources. v1 does not support \`defaults.blocks\`.`,
4413
+ description: `Simplified page-structure request object for applyBlueprint. \`version\` may be omitted and defaults to '1'. Runtime validation enforces mode-specific rules: create does not accept target, while replace requires target.pageSchemaUid and does not use navigation. In create mode, \`navigation.layoutUid\` scopes menu group reuse, duplicate page identity, and newly created routes to the target layout; use \`mobile-layout-model\` for mobile pages, where \`navigation.group\` is ignored and the page is created as a root tab, and omit it for default admin behavior. For JS blocks/fields/actions, \`script\` is a non-empty string asset key into \`assets.scripts\`, and referenced script assets must provide non-empty \`code\`; put inline JS in \`settings.code\` and \`settings.version\`. ${TREE_TABLE_RECORD_ACTION_DEFAULTS_NOTE} ${APPLY_BLUEPRINT_TREE_TABLE_TITLE_FIELD_NOTE} \`defaults.collections\` may provide main data-source collection-level fieldGroups, popup metadata with required \`name\` and \`description\`, and formBehavior for generated default add/edit popup forms; use \`defaults.dataSources.<dataSourceKey>.collections\` for external data sources. v1 does not support \`defaults.blocks\`.`,
4409
4414
  properties: {
4410
4415
  version: {
4411
4416
  type: "string",
@@ -4577,6 +4582,10 @@ const schemas = {
4577
4582
  type: "object",
4578
4583
  required: ["title"],
4579
4584
  properties: {
4585
+ layoutUid: {
4586
+ type: "string",
4587
+ description: "Optional enabled UI layout uid for the created menu route, for example `mobile-layout-model`. When omitted, the route inherits its parent layout or the default admin layout."
4588
+ },
4580
4589
  title: {
4581
4590
  type: "string"
4582
4591
  },
@@ -4673,6 +4682,10 @@ const schemas = {
4673
4682
  FlowSurfaceCreatePageRequest: {
4674
4683
  type: "object",
4675
4684
  properties: {
4685
+ layoutUid: {
4686
+ type: "string",
4687
+ description: "Optional enabled UI layout uid. With `menuRouteId`, the existing route must already belong to this layout. Without `menuRouteId`, the compatibility create-menu fallback creates the route in this layout."
4688
+ },
4676
4689
  menuRouteId: STRING_OR_INTEGER_SCHEMA,
4677
4690
  pageSchemaUid: {
4678
4691
  type: "string"