@nocobase/plugin-flow-engine 2.2.0-beta.9 → 2.3.0-alpha.1

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 (37) hide show
  1. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextApis.js +2 -2
  2. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextEnvs.js +2 -2
  3. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextVars.js +2 -2
  4. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/lintAndTestJS.js +2 -2
  5. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/patchJSCode.js +2 -2
  6. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/readJSCode.js +2 -2
  7. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/writeJSCode.js +2 -2
  8. package/dist/ai/docs/runjs/context/ai.md +206 -0
  9. package/dist/externalVersion.js +9 -9
  10. package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
  11. package/dist/node_modules/acorn/package.json +1 -1
  12. package/dist/node_modules/acorn-jsx/package.json +1 -1
  13. package/dist/node_modules/acorn-walk/package.json +1 -1
  14. package/dist/node_modules/ses/package.json +1 -1
  15. package/dist/node_modules/zod/package.json +1 -1
  16. package/dist/server/flow-surfaces/authoring-validation.d.ts +2 -1
  17. package/dist/server/flow-surfaces/authoring-validation.js +167 -19
  18. package/dist/server/flow-surfaces/blueprint/compile-blocks.js +7 -3
  19. package/dist/server/flow-surfaces/blueprint/compile-plan.js +19 -15
  20. package/dist/server/flow-surfaces/blueprint/normalize-document.js +18 -2
  21. package/dist/server/flow-surfaces/blueprint/public-types.d.ts +1 -0
  22. package/dist/server/flow-surfaces/builder.js +3 -0
  23. package/dist/server/flow-surfaces/constants.d.ts +9 -2
  24. package/dist/server/flow-surfaces/constants.js +1 -0
  25. package/dist/server/flow-surfaces/contract-guard.js +20 -10
  26. package/dist/server/flow-surfaces/errors.d.ts +2 -2
  27. package/dist/server/flow-surfaces/errors.js +4 -4
  28. package/dist/server/flow-surfaces/navigation-targets.d.ts +73 -0
  29. package/dist/server/flow-surfaces/navigation-targets.js +439 -0
  30. package/dist/server/flow-surfaces/runjs-authoring/runtime/constants.js +1 -0
  31. package/dist/server/flow-surfaces/service.d.ts +14 -2
  32. package/dist/server/flow-surfaces/service.js +471 -115
  33. package/dist/server/variables/records.js +7 -2
  34. package/dist/swagger/flow-surfaces.d.ts +96 -0
  35. package/dist/swagger/flow-surfaces.js +88 -7
  36. package/dist/swagger/index.d.ts +96 -0
  37. package/package.json +2 -2
@@ -66,6 +66,7 @@ var import_runtime = require("./planning/runtime");
66
66
  var import_created_keys = require("./planning/created-keys");
67
67
  var import_key_persistence = require("./planning/key-persistence");
68
68
  var import_payload_shape = require("./payload-shape");
69
+ var import_navigation_targets = require("./navigation-targets");
69
70
  var import_field_semantics = require("./field-semantics");
70
71
  var import_field_grid_layout = require("./field-grid-layout");
71
72
  var import_authoring_validation = require("./authoring-validation");
@@ -689,7 +690,6 @@ function assertNoFlowSurfaceIdTitleFieldSettings(value, options = {}) {
689
690
  };
690
691
  visit(value, options.basePath || []);
691
692
  }
692
- const DEFAULT_ADMIN_UI_LAYOUT_UID = "admin-layout-model";
693
693
  const MOBILE_UI_LAYOUT_TYPE = "mobile";
694
694
  class FlowSurfacesService {
695
695
  constructor(plugin) {
@@ -711,6 +711,9 @@ class FlowSurfacesService {
711
711
  (values, options) => this.patchFlowSurfaceModelOptions(values, options)
712
712
  );
713
713
  }
714
+ get navigationTargets() {
715
+ return new import_navigation_targets.FlowSurfaceNavigationTargetsService(this.db);
716
+ }
714
717
  getFlowTemplateRepository(actionName) {
715
718
  try {
716
719
  const repo = this.db.getRepository("flowModelTemplates");
@@ -924,7 +927,7 @@ class FlowSurfacesService {
924
927
  transaction
925
928
  });
926
929
  }
927
- async findMenuGroupRoutesByTitle(title, transaction, layoutUid) {
930
+ async findMenuGroupRoutesByTitle(title, transaction, layoutUid, portalUid) {
928
931
  const normalizedTitle = String(title || "").trim();
929
932
  if (!normalizedTitle) {
930
933
  return [];
@@ -938,6 +941,20 @@ class FlowSurfacesService {
938
941
  transaction
939
942
  })
940
943
  );
944
+ const normalizedPortalUid = this.navigationTargets.normalizePortalUid(portalUid);
945
+ if (normalizedPortalUid) {
946
+ const matchedRoutes2 = [];
947
+ for (const route of routes) {
948
+ const routePortalUids = await this.navigationTargets.readRoutePortalUids(
949
+ this.readRouteField(route, "id"),
950
+ transaction
951
+ );
952
+ if (routePortalUids.includes(normalizedPortalUid)) {
953
+ matchedRoutes2.push(route);
954
+ }
955
+ }
956
+ return matchedRoutes2;
957
+ }
941
958
  const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
942
959
  if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
943
960
  return routes;
@@ -957,13 +974,13 @@ class FlowSurfacesService {
957
974
  }
958
975
  return String(routeParentId ?? "") === String(parentId ?? "");
959
976
  }
960
- async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid) {
961
- const routes = await this.findMenuGroupRoutesByTitle(title, transaction, layoutUid);
977
+ async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid, portalUid) {
978
+ const routes = await this.findMenuGroupRoutesByTitle(title, transaction, layoutUid, portalUid);
962
979
  return routes.filter(
963
980
  (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
964
981
  );
965
982
  }
966
- async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid) {
983
+ async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid, portalUid) {
967
984
  const normalizedTitle = String(title || "").trim();
968
985
  if (!normalizedTitle) {
969
986
  return [];
@@ -978,6 +995,22 @@ class FlowSurfacesService {
978
995
  transaction
979
996
  })
980
997
  );
998
+ const normalizedPortalUid = this.navigationTargets.normalizePortalUid(portalUid);
999
+ if (normalizedPortalUid) {
1000
+ const portalMatchedRoutes = [];
1001
+ for (const route of routes) {
1002
+ const routePortalUids = await this.navigationTargets.readRoutePortalUids(
1003
+ this.readRouteField(route, "id"),
1004
+ transaction
1005
+ );
1006
+ if (routePortalUids.includes(normalizedPortalUid)) {
1007
+ portalMatchedRoutes.push(route);
1008
+ }
1009
+ }
1010
+ return portalMatchedRoutes.filter(
1011
+ (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
1012
+ );
1013
+ }
981
1014
  const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
982
1015
  const layoutMatchedRoutes = [];
983
1016
  if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
@@ -1091,12 +1124,12 @@ class FlowSurfacesService {
1091
1124
  }
1092
1125
  const defaultLayout = await this.db.getRepository("uiLayouts").findOne({
1093
1126
  filter: {
1094
- uid: DEFAULT_ADMIN_UI_LAYOUT_UID,
1127
+ uid: import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID,
1095
1128
  enabled: true
1096
1129
  },
1097
1130
  transaction
1098
1131
  });
1099
- return defaultLayout ? [DEFAULT_ADMIN_UI_LAYOUT_UID] : [];
1132
+ return defaultLayout ? [import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID] : [];
1100
1133
  }
1101
1134
  async findDesktopRouteUiLayoutByUid(layoutUid, transaction) {
1102
1135
  const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
@@ -1147,6 +1180,199 @@ class FlowSurfacesService {
1147
1180
  }
1148
1181
  return this.resolveDefaultDesktopRouteUiLayoutUids(transaction);
1149
1182
  }
1183
+ assertNavigationTargetIsExclusive(actionName, layoutUid, portalUid, paths) {
1184
+ const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
1185
+ const normalizedPortalUid = this.navigationTargets.normalizePortalUid(portalUid);
1186
+ if (!normalizedLayoutUid || !normalizedPortalUid) {
1187
+ return;
1188
+ }
1189
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} cannot use layoutUid and portalUid together`, {
1190
+ ruleId: "navigation-target-mutually-exclusive",
1191
+ path: paths.portal,
1192
+ details: {
1193
+ layoutUid: normalizedLayoutUid,
1194
+ portalUid: normalizedPortalUid,
1195
+ layoutPath: paths.layout,
1196
+ portalPath: paths.portal
1197
+ }
1198
+ });
1199
+ }
1200
+ async readDesktopRouteScope(route, transaction) {
1201
+ const routeId = this.readRouteField(route, "id");
1202
+ return {
1203
+ portalUids: await this.navigationTargets.readRoutePortalUids(routeId, transaction),
1204
+ layoutUids: await this.readDesktopRouteUiLayoutUids(routeId, transaction)
1205
+ };
1206
+ }
1207
+ async resolveRequestedDesktopRouteScope(values, parentRoute, actionName, options = {}) {
1208
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
1209
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
1210
+ this.assertNavigationTargetIsExclusive(actionName, layoutUid, portalUid, {
1211
+ layout: "values.layoutUid",
1212
+ portal: "values.portalUid"
1213
+ });
1214
+ if (portalUid) {
1215
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
1216
+ actionName,
1217
+ path: "values.portalUid",
1218
+ currentRoles: options.currentRoles,
1219
+ transaction: options.transaction
1220
+ });
1221
+ if (parentRoute) {
1222
+ await this.navigationTargets.assertRouteBelongsToPortal(
1223
+ actionName,
1224
+ this.readRouteField(parentRoute, "id"),
1225
+ portal.uid,
1226
+ "values.parentMenuRouteId",
1227
+ options.transaction
1228
+ );
1229
+ }
1230
+ return {
1231
+ portalUids: [portal.uid],
1232
+ layoutUids: [],
1233
+ selectedPortal: portal,
1234
+ layoutType: portal.layoutType
1235
+ };
1236
+ }
1237
+ if (layoutUid) {
1238
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
1239
+ if (parentRoute) {
1240
+ await this.assertDesktopRouteBelongsToUiLayout(
1241
+ actionName,
1242
+ parentRoute,
1243
+ layoutUid,
1244
+ "values.parentMenuRouteId",
1245
+ options.transaction
1246
+ );
1247
+ }
1248
+ return {
1249
+ portalUids: [],
1250
+ layoutUids: [layoutUid],
1251
+ layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
1252
+ };
1253
+ }
1254
+ if (parentRoute) {
1255
+ const inheritedScope = await this.readDesktopRouteScope(parentRoute, options.transaction);
1256
+ if (inheritedScope.portalUids.length > 1) {
1257
+ (0, import_errors.throwBadRequest)(
1258
+ `flowSurfaces ${actionName} parent route belongs to multiple portals; pass values.portalUid explicitly`,
1259
+ {
1260
+ ruleId: "navigation-route-scope-ambiguous",
1261
+ path: "values.parentMenuRouteId",
1262
+ details: {
1263
+ routeId: this.readRouteField(parentRoute, "id"),
1264
+ portalUids: inheritedScope.portalUids
1265
+ }
1266
+ }
1267
+ );
1268
+ }
1269
+ if (inheritedScope.portalUids.length === 1) {
1270
+ const portal = await this.navigationTargets.resolvePortal(inheritedScope.portalUids[0], {
1271
+ actionName,
1272
+ path: "values.parentMenuRouteId",
1273
+ currentRoles: options.currentRoles,
1274
+ transaction: options.transaction
1275
+ });
1276
+ return {
1277
+ ...inheritedScope,
1278
+ selectedPortal: portal,
1279
+ layoutType: portal.layoutType
1280
+ };
1281
+ }
1282
+ }
1283
+ const layoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, options.transaction);
1284
+ return {
1285
+ portalUids: [],
1286
+ layoutUids,
1287
+ layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
1288
+ };
1289
+ }
1290
+ async resolveExistingDesktopRouteScope(values, route, actionName, options = {}) {
1291
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
1292
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
1293
+ this.assertNavigationTargetIsExclusive(actionName, layoutUid, portalUid, {
1294
+ layout: "values.layoutUid",
1295
+ portal: "values.portalUid"
1296
+ });
1297
+ const routeScope = await this.readDesktopRouteScope(route, options.transaction);
1298
+ if (portalUid) {
1299
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
1300
+ actionName,
1301
+ path: "values.portalUid",
1302
+ currentRoles: options.currentRoles,
1303
+ transaction: options.transaction
1304
+ });
1305
+ await this.navigationTargets.assertRouteBelongsToPortal(
1306
+ actionName,
1307
+ this.readRouteField(route, "id"),
1308
+ portal.uid,
1309
+ "values.menuRouteId",
1310
+ options.transaction
1311
+ );
1312
+ return {
1313
+ ...routeScope,
1314
+ selectedPortal: portal,
1315
+ layoutType: portal.layoutType
1316
+ };
1317
+ }
1318
+ if (layoutUid) {
1319
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
1320
+ await this.assertDesktopRouteBelongsToUiLayout(
1321
+ actionName,
1322
+ route,
1323
+ layoutUid,
1324
+ "values.menuRouteId",
1325
+ options.transaction
1326
+ );
1327
+ return {
1328
+ ...routeScope,
1329
+ layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
1330
+ };
1331
+ }
1332
+ if (routeScope.portalUids.length > 1) {
1333
+ (0, import_errors.throwBadRequest)(
1334
+ `flowSurfaces ${actionName} menu route belongs to multiple portals; pass values.portalUid explicitly`,
1335
+ {
1336
+ ruleId: "navigation-route-scope-ambiguous",
1337
+ path: "values.menuRouteId",
1338
+ details: {
1339
+ routeId: this.readRouteField(route, "id"),
1340
+ portalUids: routeScope.portalUids
1341
+ }
1342
+ }
1343
+ );
1344
+ }
1345
+ if (routeScope.portalUids.length === 1) {
1346
+ const portal = await this.navigationTargets.resolvePortal(routeScope.portalUids[0], {
1347
+ actionName,
1348
+ path: "values.menuRouteId",
1349
+ currentRoles: options.currentRoles,
1350
+ transaction: options.transaction
1351
+ });
1352
+ return {
1353
+ ...routeScope,
1354
+ selectedPortal: portal,
1355
+ layoutType: portal.layoutType
1356
+ };
1357
+ }
1358
+ const layoutUids = routeScope.layoutUids.length ? routeScope.layoutUids : await this.ensureDesktopRouteUiLayouts(route, options.transaction);
1359
+ return {
1360
+ portalUids: [],
1361
+ layoutUids,
1362
+ layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
1363
+ };
1364
+ }
1365
+ async attachDesktopRouteScopeToRouteAndChildren(routeId, scope, transaction) {
1366
+ if (scope.layoutUids.length) {
1367
+ await this.attachDesktopRouteUiLayoutsToRouteAndChildren(routeId, scope.layoutUids, transaction);
1368
+ }
1369
+ if (scope.portalUids.length) {
1370
+ await this.navigationTargets.attachRouteTreeToPortals(routeId, scope.portalUids, {
1371
+ portalOnly: scope.layoutUids.length === 0,
1372
+ transaction
1373
+ });
1374
+ }
1375
+ }
1150
1376
  async assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, path, transaction) {
1151
1377
  if (!this.hasDesktopRouteUiLayoutsRelation()) {
1152
1378
  (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} requires plugin-ui-layout desktop route relation support`, {
@@ -1194,23 +1420,6 @@ class FlowSurfacesService {
1194
1420
  }
1195
1421
  });
1196
1422
  }
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
- }
1214
1423
  async setDesktopRouteUiLayouts(routeId, uiLayoutUids, transaction) {
1215
1424
  const relationRouteId = this.normalizeDesktopRouteRelationId(routeId);
1216
1425
  if (import_lodash.default.isUndefined(relationRouteId) || !uiLayoutUids.length || !this.hasDesktopRouteUiLayoutsRelation()) {
@@ -1308,21 +1517,18 @@ class FlowSurfacesService {
1308
1517
  });
1309
1518
  }
1310
1519
  }
1311
- async createFlowMenuGroup(values, transaction) {
1312
- const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1520
+ async createFlowMenuGroup(values, options = {}) {
1521
+ var _a;
1522
+ const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
1313
1523
  const parentId = this.readRouteField(parentRoute, "id") ?? null;
1314
- const routeUiLayoutUids = await this.resolveRequestedDesktopRouteUiLayoutUids(
1315
- values,
1316
- parentRoute,
1317
- "createMenu",
1318
- transaction
1319
- );
1524
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1320
1525
  const title = String(values.title || "").trim();
1321
1526
  const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(
1322
1527
  parentId,
1323
1528
  title,
1324
- transaction,
1325
- routeUiLayoutUids
1529
+ options.transaction,
1530
+ routeScope.layoutUids,
1531
+ (_a = routeScope.selectedPortal) == null ? void 0 : _a.uid
1326
1532
  );
1327
1533
  if (existingGroups.length === 1) {
1328
1534
  return this.buildMenuResult(existingGroups[0]);
@@ -1359,24 +1565,19 @@ class FlowSurfacesService {
1359
1565
  hideInMenu: !!values.hideInMenu,
1360
1566
  parentId
1361
1567
  },
1362
- transaction
1568
+ transaction: options.transaction
1363
1569
  });
1364
- await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1570
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1365
1571
  this.readRouteField(route, "id"),
1366
- routeUiLayoutUids,
1367
- transaction
1572
+ routeScope,
1573
+ options.transaction
1368
1574
  );
1369
1575
  return this.buildMenuResult(route);
1370
1576
  }
1371
- async createFlowMenuItem(values, transaction) {
1577
+ async createFlowMenuItem(values, options = {}) {
1372
1578
  var _a;
1373
- const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1374
- const routeUiLayoutUids = await this.resolveRequestedDesktopRouteUiLayoutUids(
1375
- values,
1376
- parentRoute,
1377
- "createMenu",
1378
- transaction
1379
- );
1579
+ const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
1580
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1380
1581
  this.assertVisibleNavigationIcon("createMenu", "values", values);
1381
1582
  const pageSchemaUid = values.pageSchemaUid || (0, import_utils.uid)();
1382
1583
  const menuSchemaUid = (0, import_utils.uid)();
@@ -1419,20 +1620,20 @@ class FlowSurfacesService {
1419
1620
  }
1420
1621
  ]
1421
1622
  },
1422
- transaction
1623
+ transaction: options.transaction
1423
1624
  });
1424
- await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1625
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1425
1626
  this.readRouteField(createdRoute, "id"),
1426
- routeUiLayoutUids,
1427
- transaction
1627
+ routeScope,
1628
+ options.transaction
1428
1629
  );
1429
1630
  const route = await desktopRoutes.findOne({
1430
1631
  filter: { schemaUid: pageSchemaUid },
1431
1632
  appends: ["children"],
1432
- transaction
1633
+ transaction: options.transaction
1433
1634
  });
1434
1635
  const tabRoute = import_lodash.default.sortBy(import_lodash.default.castArray(((_a = route == null ? void 0 : route.get) == null ? void 0 : _a.call(route, "children")) || []), "sort")[0];
1435
- await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, transaction);
1636
+ await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, options.transaction);
1436
1637
  const pageTree = (0, import_builder.buildPersistedRootPageModel)({
1437
1638
  pageUid,
1438
1639
  pageTitle: title,
@@ -1448,7 +1649,7 @@ class FlowSurfacesService {
1448
1649
  subType: "object",
1449
1650
  ...pageTree
1450
1651
  },
1451
- { transaction }
1652
+ { transaction: options.transaction }
1452
1653
  );
1453
1654
  return this.buildMenuResult(route, {
1454
1655
  pageSchemaUid,
@@ -1460,7 +1661,16 @@ class FlowSurfacesService {
1460
1661
  });
1461
1662
  }
1462
1663
  sanitizePublicCreateMenuValues(values) {
1463
- return import_lodash.default.pick(values, ["title", "type", "layoutUid", "icon", "tooltip", "hideInMenu", "parentMenuRouteId"]);
1664
+ return import_lodash.default.pick(values, [
1665
+ "title",
1666
+ "type",
1667
+ "layoutUid",
1668
+ "portalUid",
1669
+ "icon",
1670
+ "tooltip",
1671
+ "hideInMenu",
1672
+ "parentMenuRouteId"
1673
+ ]);
1464
1674
  }
1465
1675
  async loadRouteBackedPageStructure(route, transaction) {
1466
1676
  const routeId = this.readRouteField(route, "id");
@@ -1834,7 +2044,7 @@ class FlowSurfacesService {
1834
2044
  return !blockScenes.some((scene) => this.isPopupCollectionBlockSceneUnsupported(scene));
1835
2045
  }
1836
2046
  resolveFieldAssociationContextFromBinding(input) {
1837
- var _a;
2047
+ var _a, _b;
1838
2048
  const parsed = this.parseFieldPath(
1839
2049
  input.collection,
1840
2050
  input.fieldPath,
@@ -1851,13 +2061,14 @@ class FlowSurfacesService {
1851
2061
  );
1852
2062
  const targetCollection = (associationField ? resolveTargetCollection(associationField) || (associationField == null ? void 0 : associationField.targetCollection) : null) || resolveTargetCollection(leafField) || (leafField == null ? void 0 : leafField.targetCollection) || null;
1853
2063
  const targetCollectionName = (0, import_service_helpers.getCollectionName)(targetCollection) || (associationField ? (0, import_service_helpers.getFieldTarget)(associationField) : void 0) || parsed.collectionName || input.collectionName;
1854
- const associationName = parsed.associationPathName ? `${parsed.collectionName}.${parsed.associationPathName}` : associationField ? (0, import_service_helpers.resolveAssociationNameFromField)(associationField, input.collection) : void 0;
2064
+ const fallbackAssociationName = parsed.associationPathName ? `${parsed.collectionName}.${parsed.associationPathName}` : void 0;
2065
+ const associationName = associationField ? ((_a = parsed.associationPathName) == null ? void 0 : _a.includes(".")) ? fallbackAssociationName : (0, import_service_helpers.resolveAssociationNameFromField)(associationField, input.collection) : void 0;
1855
2066
  return {
1856
2067
  associationField,
1857
2068
  targetCollection,
1858
- dataSourceKey: (targetCollection == null ? void 0 : targetCollection.dataSourceKey) || ((_a = targetCollection == null ? void 0 : targetCollection.options) == null ? void 0 : _a.dataSourceKey) || parsed.dataSourceKey || input.dataSourceKey,
2069
+ dataSourceKey: (targetCollection == null ? void 0 : targetCollection.dataSourceKey) || ((_b = targetCollection == null ? void 0 : targetCollection.options) == null ? void 0 : _b.dataSourceKey) || parsed.dataSourceKey || input.dataSourceKey,
1859
2070
  collectionName: targetCollectionName,
1860
- associationName
2071
+ associationName: associationName || fallbackAssociationName
1861
2072
  };
1862
2073
  }
1863
2074
  async resolvePopupHostFieldAssociationContext(hostNode, transaction) {
@@ -3371,11 +3582,13 @@ class FlowSurfacesService {
3371
3582
  return document;
3372
3583
  }
3373
3584
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(document.navigation.layoutUid);
3585
+ const portalUid = this.navigationTargets.normalizePortalUid(document.navigation.portalUid);
3374
3586
  const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(
3375
3587
  null,
3376
3588
  groupTitle,
3377
3589
  transaction,
3378
- layoutUid || await this.resolveDefaultDesktopRouteUiLayoutUids(transaction)
3590
+ layoutUid || await this.resolveDefaultDesktopRouteUiLayoutUids(transaction),
3591
+ portalUid
3379
3592
  );
3380
3593
  if (!matchedRoutes.length) {
3381
3594
  return document;
@@ -3401,13 +3614,20 @@ class FlowSurfacesService {
3401
3614
  }
3402
3615
  };
3403
3616
  }
3404
- async normalizeApplyBlueprintCreateMobileNavigation(document, transaction) {
3405
- var _a;
3617
+ async normalizeApplyBlueprintCreateMobileNavigation(document, options = {}) {
3618
+ var _a, _b;
3406
3619
  if (document.mode !== "create") {
3407
3620
  return document;
3408
3621
  }
3409
3622
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3410
- if (!await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3623
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = document.navigation) == null ? void 0 : _b.portalUid);
3624
+ const layoutType = portalUid ? (await this.navigationTargets.resolvePortal(portalUid, {
3625
+ actionName: "applyBlueprint",
3626
+ path: "values.navigation.portalUid",
3627
+ currentRoles: options.currentRoles,
3628
+ transaction: options.transaction
3629
+ })).layoutType : await this.getDesktopRouteUiLayoutTypeByUid(layoutUid || "", options.transaction);
3630
+ if (layoutType !== MOBILE_UI_LAYOUT_TYPE) {
3411
3631
  return document;
3412
3632
  }
3413
3633
  return {
@@ -3415,73 +3635,125 @@ class FlowSurfacesService {
3415
3635
  navigation: (0, import_service_utils.buildDefinedPayload)({
3416
3636
  ...document.navigation,
3417
3637
  layoutUid,
3638
+ portalUid,
3418
3639
  group: void 0
3419
3640
  })
3420
3641
  };
3421
3642
  }
3422
- async assertApplyBlueprintCreateNavigationLayout(document, transaction) {
3423
- var _a, _b, _c;
3643
+ async assertApplyBlueprintCreateNavigationTarget(document, options = {}) {
3644
+ var _a, _b, _c, _d, _e, _f;
3424
3645
  if (document.mode !== "create") {
3425
3646
  return;
3426
3647
  }
3427
3648
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3428
- if (!layoutUid) {
3649
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = document.navigation) == null ? void 0 : _b.portalUid);
3650
+ this.assertNavigationTargetIsExclusive("applyBlueprint", layoutUid, portalUid, {
3651
+ layout: "values.navigation.layoutUid",
3652
+ portal: "values.navigation.portalUid"
3653
+ });
3654
+ if (!layoutUid && !portalUid) {
3655
+ return;
3656
+ }
3657
+ if (portalUid) {
3658
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
3659
+ actionName: "applyBlueprint",
3660
+ path: "values.navigation.portalUid",
3661
+ currentRoles: options.currentRoles,
3662
+ transaction: options.transaction
3663
+ });
3664
+ if (portal.layoutType === MOBILE_UI_LAYOUT_TYPE) {
3665
+ return;
3666
+ }
3667
+ const groupRouteId2 = (_d = (_c = document.navigation) == null ? void 0 : _c.group) == null ? void 0 : _d.routeId;
3668
+ if (import_lodash.default.isNil(groupRouteId2) || groupRouteId2 === "") {
3669
+ return;
3670
+ }
3671
+ const groupRoute2 = await this.assertMenuParentIsGroup(groupRouteId2, options.transaction);
3672
+ await this.navigationTargets.assertRouteBelongsToPortal(
3673
+ "applyBlueprint",
3674
+ this.readRouteField(groupRoute2, "id"),
3675
+ portal.uid,
3676
+ "values.navigation.group.routeId",
3677
+ options.transaction
3678
+ );
3429
3679
  return;
3430
3680
  }
3431
3681
  await this.assertEnabledDesktopRouteUiLayoutUid(
3432
3682
  layoutUid,
3433
3683
  "applyBlueprint",
3434
3684
  "values.navigation.layoutUid",
3435
- transaction
3685
+ options.transaction
3436
3686
  );
3437
- if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3687
+ if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, options.transaction)) {
3438
3688
  return;
3439
3689
  }
3440
- const groupRouteId = (_c = (_b = document.navigation) == null ? void 0 : _b.group) == null ? void 0 : _c.routeId;
3690
+ const groupRouteId = (_f = (_e = document.navigation) == null ? void 0 : _e.group) == null ? void 0 : _f.routeId;
3441
3691
  if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "") {
3442
3692
  return;
3443
3693
  }
3444
- const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, transaction);
3694
+ const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, options.transaction);
3445
3695
  await this.assertDesktopRouteBelongsToUiLayout(
3446
3696
  "applyBlueprint",
3447
3697
  groupRoute,
3448
3698
  layoutUid,
3449
3699
  "values.navigation.group.routeId",
3450
- transaction
3700
+ options.transaction
3451
3701
  );
3452
3702
  }
3453
3703
  async resolveApplyBlueprintCreatePageIdentity(document, transaction) {
3454
- var _a, _b, _c, _d, _e, _f;
3704
+ var _a, _b, _c, _d, _e, _f, _g;
3455
3705
  if (document.mode !== "create") {
3456
3706
  return document;
3457
3707
  }
3458
3708
  const groupRouteId = (_b = (_a = document.navigation) == null ? void 0 : _a.group) == null ? void 0 : _b.routeId;
3459
3709
  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();
3710
+ let portalUid = this.navigationTargets.normalizePortalUid((_d = document.navigation) == null ? void 0 : _d.portalUid);
3711
+ const pageTitle = String(((_e = document.page) == null ? void 0 : _e.title) || ((_g = (_f = document.navigation) == null ? void 0 : _f.item) == null ? void 0 : _g.title) || "").trim();
3461
3712
  const hasGroupRouteId = !import_lodash.default.isNil(groupRouteId) && groupRouteId !== "";
3462
- if (!pageTitle || !hasGroupRouteId && !layoutUid) {
3713
+ if (!pageTitle || !hasGroupRouteId && !layoutUid && !portalUid) {
3463
3714
  return document;
3464
3715
  }
3465
3716
  const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, transaction) : null;
3466
- const layoutUidFilter = layoutUid || (groupRoute ? await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, transaction) : void 0);
3717
+ if (!portalUid && groupRoute) {
3718
+ const groupPortalUids = await this.navigationTargets.readRoutePortalUids(
3719
+ this.readRouteField(groupRoute, "id"),
3720
+ transaction
3721
+ );
3722
+ if (groupPortalUids.length > 1) {
3723
+ (0, import_errors.throwBadRequest)(
3724
+ `flowSurfaces applyBlueprint navigation.group.routeId '${groupRouteId}' belongs to multiple portals; pass navigation.portalUid explicitly`,
3725
+ {
3726
+ ruleId: "navigation-route-scope-ambiguous",
3727
+ path: "values.navigation.group.routeId",
3728
+ details: {
3729
+ routeId: groupRouteId,
3730
+ portalUids: groupPortalUids
3731
+ }
3732
+ }
3733
+ );
3734
+ }
3735
+ portalUid = groupPortalUids[0];
3736
+ }
3737
+ const layoutUidFilter = portalUid ? void 0 : layoutUid || (groupRoute ? await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, transaction) : void 0);
3467
3738
  const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(
3468
3739
  hasGroupRouteId ? groupRouteId : null,
3469
3740
  pageTitle,
3470
3741
  transaction,
3471
- layoutUidFilter
3742
+ layoutUidFilter,
3743
+ portalUid
3472
3744
  );
3473
3745
  if (!matchedPages.length) {
3474
3746
  return document;
3475
3747
  }
3476
3748
  if (matchedPages.length > 1) {
3477
- const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3749
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3478
3750
  (0, import_errors.throwBadRequest)(
3479
3751
  `flowSurfaces applyBlueprint ${locationLabel} already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3480
3752
  );
3481
3753
  }
3482
3754
  const pageSchemaUid = String(this.readRouteField(matchedPages[0], "schemaUid") || "").trim();
3483
3755
  if (!pageSchemaUid) {
3484
- const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3756
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3485
3757
  (0, import_errors.throwBadRequest)(
3486
3758
  `flowSurfaces applyBlueprint existing flow page '${pageTitle}' ${locationLabel} is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3487
3759
  );
@@ -3495,21 +3767,18 @@ class FlowSurfacesService {
3495
3767
  navigation: void 0
3496
3768
  };
3497
3769
  }
3498
- async prepareApplyBlueprintRequest(values, transaction, createdKanbanSortFields) {
3770
+ async prepareApplyBlueprintRequest(values, options = {}, createdKanbanSortFields) {
3499
3771
  var _a;
3500
3772
  const initialDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
3501
- await this.assertApplyBlueprintCreateNavigationLayout(initialDocument, transaction);
3502
- const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(
3503
- initialDocument,
3504
- transaction
3505
- );
3773
+ await this.assertApplyBlueprintCreateNavigationTarget(initialDocument, options);
3774
+ const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(initialDocument, options);
3506
3775
  const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(
3507
3776
  mobileNormalizedDocument,
3508
- transaction
3777
+ options.transaction
3509
3778
  );
3510
- const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, transaction);
3511
- await this.prepareApplyBlueprintKanbanBlocks(document, transaction, createdKanbanSortFields);
3512
- const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, transaction) : void 0;
3779
+ const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, options.transaction);
3780
+ await this.prepareApplyBlueprintKanbanBlocks(document, options.transaction, createdKanbanSortFields);
3781
+ const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, options.transaction) : void 0;
3513
3782
  if (document.mode === "replace" && document.tabs.length > 1 && typeof ((_a = document.page) == null ? void 0 : _a.enableTabs) === "undefined" && (replaceTarget == null ? void 0 : replaceTarget.pageEnableTabs) === false) {
3514
3783
  (0, import_errors.throwBadRequest)(
3515
3784
  `flowSurfaces applyBlueprint replace mode requires page.enableTabs=true when tabs.length > 1 and the current page has enableTabs=false`
@@ -4159,12 +4428,36 @@ class FlowSurfacesService {
4159
4428
  }
4160
4429
  }
4161
4430
  async assertApplyBlueprintAuthoringPayload(values, options = {}) {
4431
+ var _a, _b;
4162
4432
  const enabledPackages = await this.resolveEnabledPluginPackages(options);
4433
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = values == null ? void 0 : values.navigation) == null ? void 0 : _a.layoutUid);
4434
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = values == null ? void 0 : values.navigation) == null ? void 0 : _b.portalUid);
4435
+ this.assertNavigationTargetIsExclusive("applyBlueprint", layoutUid, portalUid, {
4436
+ layout: "values.navigation.layoutUid",
4437
+ portal: "values.navigation.portalUid"
4438
+ });
4439
+ const resolvedPortal = portalUid ? await this.navigationTargets.resolvePortal(portalUid, {
4440
+ actionName: "applyBlueprint",
4441
+ path: "values.navigation.portalUid",
4442
+ currentRoles: options.currentRoles,
4443
+ transaction: options.transaction
4444
+ }) : void 0;
4163
4445
  await (0, import_authoring_validation.assertFlowSurfaceAuthoringPayload)("applyBlueprint", values, {
4164
4446
  transaction: options.transaction,
4165
4447
  enabledPackages,
4166
- findMenuGroupRoutesByTitle: (title, transaction, layoutUid) => this.findMenuGroupRoutesByTitle(title, transaction, layoutUid),
4167
- getUiLayoutTypeByUid: (layoutUid, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction),
4448
+ findMenuGroupRoutesByTitle: (title, transaction, targetLayoutUid, targetPortalUid) => this.findMenuGroupRoutesByTitle(title, transaction, targetLayoutUid, targetPortalUid),
4449
+ getUiLayoutTypeByUid: (layoutUid2, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid2, transaction),
4450
+ getPortalLayoutTypeByUid: async (targetPortalUid, transaction) => {
4451
+ if ((resolvedPortal == null ? void 0 : resolvedPortal.uid) === targetPortalUid) {
4452
+ return resolvedPortal.layoutType;
4453
+ }
4454
+ return (await this.navigationTargets.resolvePortal(targetPortalUid, {
4455
+ actionName: "applyBlueprint",
4456
+ path: "values.navigation.portalUid",
4457
+ currentRoles: options.currentRoles,
4458
+ transaction
4459
+ })).layoutType;
4460
+ },
4168
4461
  getCollection: (dataSourceKey, collectionName) => this.getCollection(dataSourceKey || "main", collectionName || "")
4169
4462
  });
4170
4463
  }
@@ -4172,7 +4465,7 @@ class FlowSurfacesService {
4172
4465
  if (options.skipAuthoringValidation !== true) {
4173
4466
  await this.assertApplyBlueprintAuthoringPayload(values, options);
4174
4467
  }
4175
- const prepared = await this.prepareApplyBlueprintRequest(values, options.transaction, createdKanbanSortFields);
4468
+ const prepared = await this.prepareApplyBlueprintRequest(values, options, createdKanbanSortFields);
4176
4469
  const popupTemplateAliasSession = this.createPopupTemplateAliasSession();
4177
4470
  const popupTemplateTreeCache = /* @__PURE__ */ new Map();
4178
4471
  this.validateApplyBlueprintPopupTemplateAliases(prepared.document, popupTemplateAliasSession);
@@ -4803,6 +5096,7 @@ class FlowSurfacesService {
4803
5096
  );
4804
5097
  }
4805
5098
  const popupProfile = await this.resolvePopupBlockProfile(node.uid, null, node, transaction).catch(() => null);
5099
+ const sourceId = String(openView.sourceId || "").trim() || ((popupProfile == null ? void 0 : popupProfile.sourceIdInferred) ? "" : String((popupProfile == null ? void 0 : popupProfile.sourceId) || "").trim()) || void 0;
4806
5100
  const popupSourceNode = popupTargetUid ? await this.repository.findModelById(popupTargetUid, {
4807
5101
  transaction,
4808
5102
  includeAsyncNode: true
@@ -4816,7 +5110,7 @@ class FlowSurfacesService {
4816
5110
  collectionName: String(openView.collectionName || "").trim() || String((popupProfile == null ? void 0 : popupProfile.collectionName) || "").trim() || void 0,
4817
5111
  associationName: String(openView.associationName || "").trim() || String((popupProfile == null ? void 0 : popupProfile.associationName) || "").trim() || void 0,
4818
5112
  filterByTk: String(openView.filterByTk || "").trim() || String((popupProfile == null ? void 0 : popupProfile.filterByTk) || "").trim() || void 0,
4819
- sourceId: String(openView.sourceId || "").trim() || String((popupProfile == null ? void 0 : popupProfile.sourceId) || "").trim() || void 0,
5113
+ sourceId,
4820
5114
  openViewStep
4821
5115
  };
4822
5116
  }
@@ -6186,6 +6480,9 @@ class FlowSurfacesService {
6186
6480
  }
6187
6481
  (0, import_errors.throwBadRequest)(`flowSurfaces configure does not support configureOptions on '${(current == null ? void 0 : current.use) || resolved.uid}'`);
6188
6482
  }
6483
+ async listNavigationTargets(_values = {}, options = {}) {
6484
+ return this.navigationTargets.listNavigationTargets(options.currentRoles, options.transaction);
6485
+ }
6189
6486
  async createMenu(values, options = {}) {
6190
6487
  const publicValues = this.sanitizePublicCreateMenuValues(values || {});
6191
6488
  const type = publicValues.type === "group" ? "group" : "item";
@@ -6193,9 +6490,9 @@ class FlowSurfacesService {
6193
6490
  (0, import_errors.throwBadRequest)("flowSurfaces createMenu requires title");
6194
6491
  }
6195
6492
  if (type === "group") {
6196
- return this.createFlowMenuGroup(publicValues, options.transaction);
6493
+ return this.createFlowMenuGroup(publicValues, options);
6197
6494
  }
6198
- return this.createFlowMenuItem(publicValues, options.transaction);
6495
+ return this.createFlowMenuItem(publicValues, options);
6199
6496
  }
6200
6497
  async updateMenu(values, options = {}) {
6201
6498
  const menuRouteId = values.menuRouteId;
@@ -6205,6 +6502,53 @@ class FlowSurfacesService {
6205
6502
  const route = await this.assertMenuRouteUpdatable(menuRouteId, options.transaction);
6206
6503
  const nextParentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
6207
6504
  await this.assertMenuParentNotSelfOrDescendant(route, values.parentMenuRouteId, options.transaction);
6505
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
6506
+ this.assertNavigationTargetIsExclusive("updateMenu", values.layoutUid, portalUid, {
6507
+ layout: "values.layoutUid",
6508
+ portal: "values.portalUid"
6509
+ });
6510
+ const routeScope = await this.readDesktopRouteScope(route, options.transaction);
6511
+ if (portalUid) {
6512
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
6513
+ actionName: "updateMenu",
6514
+ path: "values.portalUid",
6515
+ currentRoles: options.currentRoles,
6516
+ transaction: options.transaction
6517
+ });
6518
+ await this.navigationTargets.assertRouteBelongsToPortal(
6519
+ "updateMenu",
6520
+ this.readRouteField(route, "id"),
6521
+ portal.uid,
6522
+ "values.menuRouteId",
6523
+ options.transaction
6524
+ );
6525
+ if (nextParentRoute) {
6526
+ await this.navigationTargets.assertRouteBelongsToPortal(
6527
+ "updateMenu",
6528
+ this.readRouteField(nextParentRoute, "id"),
6529
+ portal.uid,
6530
+ "values.parentMenuRouteId",
6531
+ options.transaction
6532
+ );
6533
+ }
6534
+ }
6535
+ if (Object.prototype.hasOwnProperty.call(values, "parentMenuRouteId") && nextParentRoute) {
6536
+ const parentScope = await this.readDesktopRouteScope(nextParentRoute, options.transaction);
6537
+ const routePortalUids = [...routeScope.portalUids].sort();
6538
+ const parentPortalUids = [...parentScope.portalUids].sort();
6539
+ if (!import_lodash.default.isEqual(routePortalUids, parentPortalUids) && (routePortalUids.length || parentPortalUids.length)) {
6540
+ (0, import_errors.throwBadRequest)(`flowSurfaces updateMenu cannot move a menu route across portal scopes`, {
6541
+ ruleId: "navigation-route-portal-mismatch",
6542
+ path: "values.parentMenuRouteId",
6543
+ details: {
6544
+ routeId: this.readRouteField(route, "id"),
6545
+ routePortalUids,
6546
+ parentRouteId: this.readRouteField(nextParentRoute, "id"),
6547
+ parentPortalUids
6548
+ }
6549
+ });
6550
+ }
6551
+ }
6208
6552
  const nextOptions = { ...this.readRouteOptions(route) };
6209
6553
  const updateValues = {};
6210
6554
  if (Object.prototype.hasOwnProperty.call(values, "title")) {
@@ -6235,25 +6579,29 @@ class FlowSurfacesService {
6235
6579
  }
6236
6580
  const updated = await this.findMenuRouteById(menuRouteId, options.transaction);
6237
6581
  if (updated && Object.prototype.hasOwnProperty.call(values, "parentMenuRouteId")) {
6238
- await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
6239
- this.readRouteField(updated, "id"),
6240
- await this.resolveInheritedDesktopRouteUiLayoutUids(nextParentRoute, options.transaction),
6241
- options.transaction
6242
- );
6582
+ if (routeScope.portalUids.length) {
6583
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6584
+ this.readRouteField(updated, "id"),
6585
+ routeScope,
6586
+ options.transaction
6587
+ );
6588
+ } else {
6589
+ await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
6590
+ this.readRouteField(updated, "id"),
6591
+ await this.resolveInheritedDesktopRouteUiLayoutUids(nextParentRoute, options.transaction),
6592
+ options.transaction
6593
+ );
6594
+ }
6243
6595
  }
6244
6596
  return this.buildMenuResult(updated);
6245
6597
  }
6246
- async initializeFlowPageForRoute(route, values, transaction) {
6598
+ async initializeFlowPageForRoute(route, values, options = {}) {
6599
+ const transaction = options.transaction;
6247
6600
  const routeId = this.readRouteField(route, "id");
6248
6601
  const pageSchemaUid = this.readRouteField(route, "schemaUid");
6249
6602
  const structure = await this.loadRouteBackedPageStructure(route, transaction);
6250
6603
  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
- }
6256
- const routeUiLayoutUids = await this.ensureDesktopRouteUiLayouts(route, transaction);
6604
+ const routeScope = await this.resolveExistingDesktopRouteScope(values, route, "createPage", options);
6257
6605
  const routeOptions = this.readRouteOptions(route);
6258
6606
  if (!routeOptions[FLOW_SURFACE_MENU_BINDABLE_OPTION_KEY]) {
6259
6607
  (0, import_errors.throwBadRequest)(`flowSurfaces createPage only accepts a bindable menu route created by createMenu(type="item")`);
@@ -6292,7 +6640,11 @@ class FlowSurfacesService {
6292
6640
  },
6293
6641
  transaction
6294
6642
  });
6295
- await this.setDesktopRouteUiLayouts(this.readRouteField(tabRoute, "id"), routeUiLayoutUids, transaction);
6643
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6644
+ this.readRouteField(tabRoute, "id"),
6645
+ routeScope,
6646
+ transaction
6647
+ );
6296
6648
  } else {
6297
6649
  await this.db.getRepository("desktopRoutes").update({
6298
6650
  filterByTk: String(this.readRouteField(tabRoute, "id")),
@@ -6317,7 +6669,11 @@ class FlowSurfacesService {
6317
6669
  });
6318
6670
  }
6319
6671
  if (tabRoute) {
6320
- await this.setDesktopRouteUiLayouts(this.readRouteField(tabRoute, "id"), routeUiLayoutUids, transaction);
6672
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6673
+ this.readRouteField(tabRoute, "id"),
6674
+ routeScope,
6675
+ transaction
6676
+ );
6321
6677
  }
6322
6678
  await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, transaction);
6323
6679
  await this.db.getRepository("desktopRoutes").update({
@@ -6369,11 +6725,11 @@ class FlowSurfacesService {
6369
6725
  let result;
6370
6726
  if (!import_lodash.default.isNil(values.menuRouteId) && values.menuRouteId !== "") {
6371
6727
  const route = await this.assertMenuRouteBindable(values.menuRouteId, options.transaction);
6372
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6728
+ result = await this.initializeFlowPageForRoute(route, values, options);
6373
6729
  } else {
6374
- const createdMenu = await this.createFlowMenuItem(values, options.transaction);
6730
+ const createdMenu = await this.createFlowMenuItem(values, options);
6375
6731
  const route = await this.assertMenuRouteBindable(createdMenu.routeId, options.transaction);
6376
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6732
+ result = await this.initializeFlowPageForRoute(route, values, options);
6377
6733
  }
6378
6734
  await this.persistCreatedKeysForAction("createPage", values, result, options.transaction);
6379
6735
  return result;
@@ -6458,11 +6814,11 @@ class FlowSurfacesService {
6458
6814
  },
6459
6815
  transaction: options.transaction
6460
6816
  });
6461
- await this.setDesktopRouteUiLayouts(
6462
- route.get("id"),
6463
- await this.ensureDesktopRouteUiLayouts(pageRoute, options.transaction),
6464
- options.transaction
6465
- );
6817
+ const pageRouteScope = await this.readDesktopRouteScope(pageRoute, options.transaction);
6818
+ if (!pageRouteScope.portalUids.length && !pageRouteScope.layoutUids.length) {
6819
+ pageRouteScope.layoutUids = await this.ensureDesktopRouteUiLayouts(pageRoute, options.transaction);
6820
+ }
6821
+ await this.attachDesktopRouteScopeToRouteAndChildren(route.get("id"), pageRouteScope, options.transaction);
6466
6822
  const gridUid = await this.ensureGridChild(tabSchemaUid, "BlockGridModel", options.transaction);
6467
6823
  const result = {
6468
6824
  pageUid: pageTarget.uid,