@nocobase/plugin-flow-engine 2.2.0-beta.9 → 3.0.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 (42) hide show
  1. package/dist/ai/ai-employees/nathan/prompt.md +2 -0
  2. package/dist/ai/ai-employees/nathan/skills/frontend-developer/SKILLS.md +1 -0
  3. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextApis.js +2 -2
  4. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextEnvs.js +2 -2
  5. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/getContextVars.js +2 -2
  6. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/lintAndTestJS.js +2 -2
  7. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/patchJSCode.js +4 -4
  8. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/readJSCode.js +2 -2
  9. package/dist/ai/ai-employees/nathan/skills/frontend-developer/tools/writeJSCode.js +2 -2
  10. package/dist/ai/docs/runjs/context/ai.md +396 -0
  11. package/dist/externalVersion.js +9 -9
  12. package/dist/locale/en-US.json +2 -0
  13. package/dist/locale/index.d.ts +4 -0
  14. package/dist/locale/zh-CN.json +2 -0
  15. package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
  16. package/dist/node_modules/acorn/package.json +1 -1
  17. package/dist/node_modules/acorn-jsx/package.json +1 -1
  18. package/dist/node_modules/acorn-walk/package.json +1 -1
  19. package/dist/node_modules/ses/package.json +1 -1
  20. package/dist/node_modules/zod/package.json +1 -1
  21. package/dist/server/flow-surfaces/authoring-validation.d.ts +2 -1
  22. package/dist/server/flow-surfaces/authoring-validation.js +167 -19
  23. package/dist/server/flow-surfaces/blueprint/compile-blocks.js +7 -3
  24. package/dist/server/flow-surfaces/blueprint/compile-plan.js +19 -15
  25. package/dist/server/flow-surfaces/blueprint/normalize-document.js +18 -2
  26. package/dist/server/flow-surfaces/blueprint/public-types.d.ts +1 -0
  27. package/dist/server/flow-surfaces/builder.js +3 -0
  28. package/dist/server/flow-surfaces/constants.d.ts +9 -2
  29. package/dist/server/flow-surfaces/constants.js +1 -0
  30. package/dist/server/flow-surfaces/contract-guard.js +20 -10
  31. package/dist/server/flow-surfaces/errors.d.ts +2 -2
  32. package/dist/server/flow-surfaces/errors.js +4 -4
  33. package/dist/server/flow-surfaces/navigation-targets.d.ts +80 -0
  34. package/dist/server/flow-surfaces/navigation-targets.js +546 -0
  35. package/dist/server/flow-surfaces/runjs-authoring/runtime/constants.js +1 -0
  36. package/dist/server/flow-surfaces/service.d.ts +21 -2
  37. package/dist/server/flow-surfaces/service.js +610 -121
  38. package/dist/server/variables/records.js +7 -2
  39. package/dist/swagger/flow-surfaces.d.ts +96 -0
  40. package/dist/swagger/flow-surfaces.js +88 -7
  41. package/dist/swagger/index.d.ts +96 -0
  42. 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,289 @@ 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
+ buildDesktopRouteScopeForPortal(portal) {
1208
+ return {
1209
+ portalUids: portal.routeScopeKind === "portal" ? [portal.uid] : [],
1210
+ layoutUids: portal.routeScopeKind === "layout" ? [portal.layoutUid] : [],
1211
+ selectedPortal: portal,
1212
+ layoutType: portal.layoutType
1213
+ };
1214
+ }
1215
+ assertAdminLayoutCreateScope(actionName, scope, path) {
1216
+ if (!this.navigationTargets.hasMultiPortalCapability() || scope.selectedPortal || !scope.layoutUids.includes(import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID)) {
1217
+ return;
1218
+ }
1219
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} cannot create navigation directly in the Admin layout`, {
1220
+ ruleId: "navigation-admin-layout-not-portal-target",
1221
+ path,
1222
+ details: {
1223
+ layoutUid: import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID,
1224
+ uiBuilderAllowed: false,
1225
+ adminLayoutFallbackAllowed: false,
1226
+ agentInstruction: "Resolve the target Portal first. For a no-code Portal, pass portalUid (navigation.portalUid in applyBlueprint); for an AI Portal, stop this Flow Surfaces write and implement the request in the Portal source code."
1227
+ }
1228
+ });
1229
+ }
1230
+ getPortalRouteFilterUid(portal) {
1231
+ return (portal == null ? void 0 : portal.routeScopeKind) === "portal" ? portal.uid : void 0;
1232
+ }
1233
+ async assertRouteBelongsToResolvedPortal(actionName, route, portal, path, transaction) {
1234
+ if (portal.routeScopeKind === "layout") {
1235
+ await this.assertDesktopRouteBelongsToUiLayout(actionName, route, portal.layoutUid, path, transaction);
1236
+ return;
1237
+ }
1238
+ await this.navigationTargets.assertRouteBelongsToPortal(
1239
+ actionName,
1240
+ this.readRouteField(route, "id"),
1241
+ portal.uid,
1242
+ path,
1243
+ transaction
1244
+ );
1245
+ }
1246
+ async resolveDefaultDesktopRouteScope(actionName, options = {}) {
1247
+ if (this.navigationTargets.hasMultiPortalCapability()) {
1248
+ const portal = await this.navigationTargets.resolveDefaultPortal({
1249
+ actionName,
1250
+ currentRoles: options.currentRoles,
1251
+ transaction: options.transaction
1252
+ });
1253
+ return this.buildDesktopRouteScopeForPortal(portal);
1254
+ }
1255
+ const layoutUids = await this.resolveDefaultDesktopRouteUiLayoutUids(options.transaction);
1256
+ return {
1257
+ portalUids: [],
1258
+ layoutUids,
1259
+ layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
1260
+ };
1261
+ }
1262
+ async resolveDesktopRouteMatchScope(layoutUid, portalUid, actionName, options = {}) {
1263
+ if (portalUid) {
1264
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
1265
+ actionName,
1266
+ path: "values.navigation.portalUid",
1267
+ currentRoles: options.currentRoles,
1268
+ transaction: options.transaction
1269
+ });
1270
+ return {
1271
+ layoutUids: portal.routeScopeKind === "layout" ? [portal.layoutUid] : void 0,
1272
+ portalUid: this.getPortalRouteFilterUid(portal)
1273
+ };
1274
+ }
1275
+ if (this.normalizeDesktopRouteLayoutUidFilter(layoutUid).length) {
1276
+ return { layoutUids: layoutUid, portalUid: void 0 };
1277
+ }
1278
+ const defaultScope = await this.resolveDefaultDesktopRouteScope(actionName, options);
1279
+ return {
1280
+ layoutUids: defaultScope.layoutUids.length ? defaultScope.layoutUids : void 0,
1281
+ portalUid: this.getPortalRouteFilterUid(defaultScope.selectedPortal)
1282
+ };
1283
+ }
1284
+ async resolveRequestedDesktopRouteScope(values, parentRoute, actionName, options = {}) {
1285
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
1286
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
1287
+ this.assertNavigationTargetIsExclusive(actionName, layoutUid, portalUid, {
1288
+ layout: "values.layoutUid",
1289
+ portal: "values.portalUid"
1290
+ });
1291
+ if (portalUid) {
1292
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
1293
+ actionName,
1294
+ path: "values.portalUid",
1295
+ currentRoles: options.currentRoles,
1296
+ transaction: options.transaction
1297
+ });
1298
+ if (parentRoute) {
1299
+ await this.assertRouteBelongsToResolvedPortal(
1300
+ actionName,
1301
+ parentRoute,
1302
+ portal,
1303
+ "values.parentMenuRouteId",
1304
+ options.transaction
1305
+ );
1306
+ }
1307
+ return this.buildDesktopRouteScopeForPortal(portal);
1308
+ }
1309
+ if (layoutUid) {
1310
+ const scope2 = { portalUids: [], layoutUids: [layoutUid] };
1311
+ this.assertAdminLayoutCreateScope(actionName, scope2, "values.layoutUid");
1312
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
1313
+ if (parentRoute) {
1314
+ await this.assertDesktopRouteBelongsToUiLayout(
1315
+ actionName,
1316
+ parentRoute,
1317
+ layoutUid,
1318
+ "values.parentMenuRouteId",
1319
+ options.transaction
1320
+ );
1321
+ }
1322
+ return {
1323
+ ...scope2,
1324
+ layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
1325
+ };
1326
+ }
1327
+ if (parentRoute) {
1328
+ const inheritedScope = await this.readDesktopRouteScope(parentRoute, options.transaction);
1329
+ if (inheritedScope.portalUids.length > 1) {
1330
+ (0, import_errors.throwBadRequest)(
1331
+ `flowSurfaces ${actionName} parent route belongs to multiple portals; pass values.portalUid explicitly`,
1332
+ {
1333
+ ruleId: "navigation-route-scope-ambiguous",
1334
+ path: "values.parentMenuRouteId",
1335
+ details: {
1336
+ routeId: this.readRouteField(parentRoute, "id"),
1337
+ portalUids: inheritedScope.portalUids
1338
+ }
1339
+ }
1340
+ );
1341
+ }
1342
+ if (inheritedScope.portalUids.length === 1) {
1343
+ const portal = await this.navigationTargets.resolvePortal(inheritedScope.portalUids[0], {
1344
+ actionName,
1345
+ path: "values.parentMenuRouteId",
1346
+ currentRoles: options.currentRoles,
1347
+ transaction: options.transaction
1348
+ });
1349
+ return {
1350
+ ...inheritedScope,
1351
+ selectedPortal: portal,
1352
+ layoutType: portal.layoutType
1353
+ };
1354
+ }
1355
+ }
1356
+ if (!parentRoute) {
1357
+ return this.resolveDefaultDesktopRouteScope(actionName, options);
1358
+ }
1359
+ const layoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, options.transaction);
1360
+ const scope = {
1361
+ portalUids: [],
1362
+ layoutUids
1363
+ };
1364
+ this.assertAdminLayoutCreateScope(actionName, scope, "values.parentMenuRouteId");
1365
+ return {
1366
+ ...scope,
1367
+ layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
1368
+ };
1369
+ }
1370
+ async resolveExistingDesktopRouteScope(values, route, actionName, options = {}) {
1371
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
1372
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
1373
+ this.assertNavigationTargetIsExclusive(actionName, layoutUid, portalUid, {
1374
+ layout: "values.layoutUid",
1375
+ portal: "values.portalUid"
1376
+ });
1377
+ const routeScope = await this.readDesktopRouteScope(route, options.transaction);
1378
+ if (portalUid) {
1379
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
1380
+ actionName,
1381
+ path: "values.portalUid",
1382
+ currentRoles: options.currentRoles,
1383
+ transaction: options.transaction
1384
+ });
1385
+ await this.assertRouteBelongsToResolvedPortal(
1386
+ actionName,
1387
+ route,
1388
+ portal,
1389
+ "values.menuRouteId",
1390
+ options.transaction
1391
+ );
1392
+ return this.buildDesktopRouteScopeForPortal(portal);
1393
+ }
1394
+ if (layoutUid) {
1395
+ const scope2 = { ...routeScope, layoutUids: [layoutUid] };
1396
+ this.assertAdminLayoutCreateScope(actionName, scope2, "values.layoutUid");
1397
+ await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
1398
+ await this.assertDesktopRouteBelongsToUiLayout(
1399
+ actionName,
1400
+ route,
1401
+ layoutUid,
1402
+ "values.menuRouteId",
1403
+ options.transaction
1404
+ );
1405
+ return {
1406
+ ...scope2,
1407
+ layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
1408
+ };
1409
+ }
1410
+ if (routeScope.portalUids.length > 1) {
1411
+ (0, import_errors.throwBadRequest)(
1412
+ `flowSurfaces ${actionName} menu route belongs to multiple portals; pass values.portalUid explicitly`,
1413
+ {
1414
+ ruleId: "navigation-route-scope-ambiguous",
1415
+ path: "values.menuRouteId",
1416
+ details: {
1417
+ routeId: this.readRouteField(route, "id"),
1418
+ portalUids: routeScope.portalUids
1419
+ }
1420
+ }
1421
+ );
1422
+ }
1423
+ if (routeScope.portalUids.length === 1) {
1424
+ const portal = await this.navigationTargets.resolvePortal(routeScope.portalUids[0], {
1425
+ actionName,
1426
+ path: "values.menuRouteId",
1427
+ currentRoles: options.currentRoles,
1428
+ transaction: options.transaction
1429
+ });
1430
+ return {
1431
+ ...routeScope,
1432
+ selectedPortal: portal,
1433
+ layoutType: portal.layoutType
1434
+ };
1435
+ }
1436
+ let layoutUids = routeScope.layoutUids;
1437
+ if (!layoutUids.length) {
1438
+ const parentRouteId = this.readRouteField(route, "parentId");
1439
+ const parentRoute = import_lodash.default.isNil(parentRouteId) ? null : await this.findMenuRouteById(parentRouteId, options.transaction);
1440
+ layoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, options.transaction);
1441
+ }
1442
+ const scope = {
1443
+ portalUids: [],
1444
+ layoutUids
1445
+ };
1446
+ this.assertAdminLayoutCreateScope(actionName, scope, "values.menuRouteId");
1447
+ if (!routeScope.layoutUids.length) {
1448
+ await this.setDesktopRouteUiLayouts(this.readRouteField(route, "id"), layoutUids, options.transaction);
1449
+ }
1450
+ return {
1451
+ ...scope,
1452
+ layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
1453
+ };
1454
+ }
1455
+ async attachDesktopRouteScopeToRouteAndChildren(routeId, scope, transaction) {
1456
+ if (scope.layoutUids.length) {
1457
+ await this.attachDesktopRouteUiLayoutsToRouteAndChildren(routeId, scope.layoutUids, transaction);
1458
+ }
1459
+ if (scope.portalUids.length) {
1460
+ await this.navigationTargets.attachRouteTreeToPortals(routeId, scope.portalUids, {
1461
+ portalOnly: scope.layoutUids.length === 0,
1462
+ transaction
1463
+ });
1464
+ }
1465
+ }
1150
1466
  async assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, path, transaction) {
1151
1467
  if (!this.hasDesktopRouteUiLayoutsRelation()) {
1152
1468
  (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} requires plugin-ui-layout desktop route relation support`, {
@@ -1194,23 +1510,6 @@ class FlowSurfacesService {
1194
1510
  }
1195
1511
  });
1196
1512
  }
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
1513
  async setDesktopRouteUiLayouts(routeId, uiLayoutUids, transaction) {
1215
1514
  const relationRouteId = this.normalizeDesktopRouteRelationId(routeId);
1216
1515
  if (import_lodash.default.isUndefined(relationRouteId) || !uiLayoutUids.length || !this.hasDesktopRouteUiLayoutsRelation()) {
@@ -1308,21 +1607,17 @@ class FlowSurfacesService {
1308
1607
  });
1309
1608
  }
1310
1609
  }
1311
- async createFlowMenuGroup(values, transaction) {
1312
- const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1610
+ async createFlowMenuGroup(values, options = {}) {
1611
+ const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
1313
1612
  const parentId = this.readRouteField(parentRoute, "id") ?? null;
1314
- const routeUiLayoutUids = await this.resolveRequestedDesktopRouteUiLayoutUids(
1315
- values,
1316
- parentRoute,
1317
- "createMenu",
1318
- transaction
1319
- );
1613
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1320
1614
  const title = String(values.title || "").trim();
1321
1615
  const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(
1322
1616
  parentId,
1323
1617
  title,
1324
- transaction,
1325
- routeUiLayoutUids
1618
+ options.transaction,
1619
+ routeScope.layoutUids,
1620
+ this.getPortalRouteFilterUid(routeScope.selectedPortal)
1326
1621
  );
1327
1622
  if (existingGroups.length === 1) {
1328
1623
  return this.buildMenuResult(existingGroups[0]);
@@ -1359,24 +1654,19 @@ class FlowSurfacesService {
1359
1654
  hideInMenu: !!values.hideInMenu,
1360
1655
  parentId
1361
1656
  },
1362
- transaction
1657
+ transaction: options.transaction
1363
1658
  });
1364
- await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1659
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1365
1660
  this.readRouteField(route, "id"),
1366
- routeUiLayoutUids,
1367
- transaction
1661
+ routeScope,
1662
+ options.transaction
1368
1663
  );
1369
1664
  return this.buildMenuResult(route);
1370
1665
  }
1371
- async createFlowMenuItem(values, transaction) {
1666
+ async createFlowMenuItem(values, options = {}) {
1372
1667
  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
- );
1668
+ const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
1669
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1380
1670
  this.assertVisibleNavigationIcon("createMenu", "values", values);
1381
1671
  const pageSchemaUid = values.pageSchemaUid || (0, import_utils.uid)();
1382
1672
  const menuSchemaUid = (0, import_utils.uid)();
@@ -1419,20 +1709,20 @@ class FlowSurfacesService {
1419
1709
  }
1420
1710
  ]
1421
1711
  },
1422
- transaction
1712
+ transaction: options.transaction
1423
1713
  });
1424
- await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1714
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1425
1715
  this.readRouteField(createdRoute, "id"),
1426
- routeUiLayoutUids,
1427
- transaction
1716
+ routeScope,
1717
+ options.transaction
1428
1718
  );
1429
1719
  const route = await desktopRoutes.findOne({
1430
1720
  filter: { schemaUid: pageSchemaUid },
1431
1721
  appends: ["children"],
1432
- transaction
1722
+ transaction: options.transaction
1433
1723
  });
1434
1724
  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);
1725
+ await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, options.transaction);
1436
1726
  const pageTree = (0, import_builder.buildPersistedRootPageModel)({
1437
1727
  pageUid,
1438
1728
  pageTitle: title,
@@ -1448,7 +1738,7 @@ class FlowSurfacesService {
1448
1738
  subType: "object",
1449
1739
  ...pageTree
1450
1740
  },
1451
- { transaction }
1741
+ { transaction: options.transaction }
1452
1742
  );
1453
1743
  return this.buildMenuResult(route, {
1454
1744
  pageSchemaUid,
@@ -1460,7 +1750,16 @@ class FlowSurfacesService {
1460
1750
  });
1461
1751
  }
1462
1752
  sanitizePublicCreateMenuValues(values) {
1463
- return import_lodash.default.pick(values, ["title", "type", "layoutUid", "icon", "tooltip", "hideInMenu", "parentMenuRouteId"]);
1753
+ return import_lodash.default.pick(values, [
1754
+ "title",
1755
+ "type",
1756
+ "layoutUid",
1757
+ "portalUid",
1758
+ "icon",
1759
+ "tooltip",
1760
+ "hideInMenu",
1761
+ "parentMenuRouteId"
1762
+ ]);
1464
1763
  }
1465
1764
  async loadRouteBackedPageStructure(route, transaction) {
1466
1765
  const routeId = this.readRouteField(route, "id");
@@ -1834,7 +2133,7 @@ class FlowSurfacesService {
1834
2133
  return !blockScenes.some((scene) => this.isPopupCollectionBlockSceneUnsupported(scene));
1835
2134
  }
1836
2135
  resolveFieldAssociationContextFromBinding(input) {
1837
- var _a;
2136
+ var _a, _b;
1838
2137
  const parsed = this.parseFieldPath(
1839
2138
  input.collection,
1840
2139
  input.fieldPath,
@@ -1851,13 +2150,14 @@ class FlowSurfacesService {
1851
2150
  );
1852
2151
  const targetCollection = (associationField ? resolveTargetCollection(associationField) || (associationField == null ? void 0 : associationField.targetCollection) : null) || resolveTargetCollection(leafField) || (leafField == null ? void 0 : leafField.targetCollection) || null;
1853
2152
  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;
2153
+ const fallbackAssociationName = parsed.associationPathName ? `${parsed.collectionName}.${parsed.associationPathName}` : void 0;
2154
+ const associationName = associationField ? ((_a = parsed.associationPathName) == null ? void 0 : _a.includes(".")) ? fallbackAssociationName : (0, import_service_helpers.resolveAssociationNameFromField)(associationField, input.collection) : void 0;
1855
2155
  return {
1856
2156
  associationField,
1857
2157
  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,
2158
+ dataSourceKey: (targetCollection == null ? void 0 : targetCollection.dataSourceKey) || ((_b = targetCollection == null ? void 0 : targetCollection.options) == null ? void 0 : _b.dataSourceKey) || parsed.dataSourceKey || input.dataSourceKey,
1859
2159
  collectionName: targetCollectionName,
1860
- associationName
2160
+ associationName: associationName || fallbackAssociationName
1861
2161
  };
1862
2162
  }
1863
2163
  async resolvePopupHostFieldAssociationContext(hostNode, transaction) {
@@ -3358,7 +3658,7 @@ class FlowSurfacesService {
3358
3658
  tabs
3359
3659
  };
3360
3660
  }
3361
- async resolveApplyBlueprintCreateNavigationGroup(document, transaction) {
3661
+ async resolveApplyBlueprintCreateNavigationGroup(document, options = {}) {
3362
3662
  var _a;
3363
3663
  if (document.mode !== "create" || !import_lodash.default.isPlainObject((_a = document.navigation) == null ? void 0 : _a.group)) {
3364
3664
  return document;
@@ -3371,11 +3671,14 @@ class FlowSurfacesService {
3371
3671
  return document;
3372
3672
  }
3373
3673
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(document.navigation.layoutUid);
3674
+ const portalUid = this.navigationTargets.normalizePortalUid(document.navigation.portalUid);
3675
+ const matchScope = await this.resolveDesktopRouteMatchScope(layoutUid, portalUid, "applyBlueprint", options);
3374
3676
  const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(
3375
3677
  null,
3376
3678
  groupTitle,
3377
- transaction,
3378
- layoutUid || await this.resolveDefaultDesktopRouteUiLayoutUids(transaction)
3679
+ options.transaction,
3680
+ matchScope.layoutUids,
3681
+ matchScope.portalUid
3379
3682
  );
3380
3683
  if (!matchedRoutes.length) {
3381
3684
  return document;
@@ -3401,13 +3704,38 @@ class FlowSurfacesService {
3401
3704
  }
3402
3705
  };
3403
3706
  }
3404
- async normalizeApplyBlueprintCreateMobileNavigation(document, transaction) {
3405
- var _a;
3707
+ async resolveApplyBlueprintDefaultNavigationTarget(document, options = {}) {
3708
+ var _a, _b, _c, _d;
3709
+ if (document.mode !== "create" || ((_a = document.navigation) == null ? void 0 : _a.layoutUid) || ((_b = document.navigation) == null ? void 0 : _b.portalUid) || !import_lodash.default.isUndefined((_d = (_c = document.navigation) == null ? void 0 : _c.group) == null ? void 0 : _d.routeId) || !this.navigationTargets.hasMultiPortalCapability()) {
3710
+ return document;
3711
+ }
3712
+ const portal = await this.navigationTargets.resolveDefaultPortal({
3713
+ actionName: "applyBlueprint",
3714
+ currentRoles: options.currentRoles,
3715
+ transaction: options.transaction
3716
+ });
3717
+ return {
3718
+ ...document,
3719
+ navigation: {
3720
+ ...document.navigation,
3721
+ portalUid: portal.uid
3722
+ }
3723
+ };
3724
+ }
3725
+ async normalizeApplyBlueprintCreateMobileNavigation(document, options = {}) {
3726
+ var _a, _b;
3406
3727
  if (document.mode !== "create") {
3407
3728
  return document;
3408
3729
  }
3409
3730
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3410
- if (!await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3731
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = document.navigation) == null ? void 0 : _b.portalUid);
3732
+ const layoutType = portalUid ? (await this.navigationTargets.resolvePortal(portalUid, {
3733
+ actionName: "applyBlueprint",
3734
+ path: "values.navigation.portalUid",
3735
+ currentRoles: options.currentRoles,
3736
+ transaction: options.transaction
3737
+ })).layoutType : await this.getDesktopRouteUiLayoutTypeByUid(layoutUid || "", options.transaction);
3738
+ if (layoutType !== MOBILE_UI_LAYOUT_TYPE) {
3411
3739
  return document;
3412
3740
  }
3413
3741
  return {
@@ -3415,73 +3743,138 @@ class FlowSurfacesService {
3415
3743
  navigation: (0, import_service_utils.buildDefinedPayload)({
3416
3744
  ...document.navigation,
3417
3745
  layoutUid,
3746
+ portalUid,
3418
3747
  group: void 0
3419
3748
  })
3420
3749
  };
3421
3750
  }
3422
- async assertApplyBlueprintCreateNavigationLayout(document, transaction) {
3423
- var _a, _b, _c;
3751
+ async assertApplyBlueprintCreateNavigationTarget(document, options = {}) {
3752
+ var _a, _b, _c, _d, _e, _f;
3424
3753
  if (document.mode !== "create") {
3425
3754
  return;
3426
3755
  }
3427
3756
  const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
3428
- if (!layoutUid) {
3757
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = document.navigation) == null ? void 0 : _b.portalUid);
3758
+ this.assertNavigationTargetIsExclusive("applyBlueprint", layoutUid, portalUid, {
3759
+ layout: "values.navigation.layoutUid",
3760
+ portal: "values.navigation.portalUid"
3761
+ });
3762
+ if (!layoutUid && !portalUid) {
3429
3763
  return;
3430
3764
  }
3765
+ if (portalUid) {
3766
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
3767
+ actionName: "applyBlueprint",
3768
+ path: "values.navigation.portalUid",
3769
+ currentRoles: options.currentRoles,
3770
+ transaction: options.transaction
3771
+ });
3772
+ if (portal.layoutType === MOBILE_UI_LAYOUT_TYPE) {
3773
+ return;
3774
+ }
3775
+ const groupRouteId2 = (_d = (_c = document.navigation) == null ? void 0 : _c.group) == null ? void 0 : _d.routeId;
3776
+ if (import_lodash.default.isNil(groupRouteId2) || groupRouteId2 === "") {
3777
+ return;
3778
+ }
3779
+ const groupRoute2 = await this.assertMenuParentIsGroup(groupRouteId2, options.transaction);
3780
+ await this.assertRouteBelongsToResolvedPortal(
3781
+ "applyBlueprint",
3782
+ groupRoute2,
3783
+ portal,
3784
+ "values.navigation.group.routeId",
3785
+ options.transaction
3786
+ );
3787
+ return;
3788
+ }
3789
+ this.assertAdminLayoutCreateScope(
3790
+ "applyBlueprint",
3791
+ { portalUids: [], layoutUids: [layoutUid] },
3792
+ "values.navigation.layoutUid"
3793
+ );
3431
3794
  await this.assertEnabledDesktopRouteUiLayoutUid(
3432
3795
  layoutUid,
3433
3796
  "applyBlueprint",
3434
3797
  "values.navigation.layoutUid",
3435
- transaction
3798
+ options.transaction
3436
3799
  );
3437
- if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, transaction)) {
3800
+ if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, options.transaction)) {
3438
3801
  return;
3439
3802
  }
3440
- const groupRouteId = (_c = (_b = document.navigation) == null ? void 0 : _b.group) == null ? void 0 : _c.routeId;
3803
+ const groupRouteId = (_f = (_e = document.navigation) == null ? void 0 : _e.group) == null ? void 0 : _f.routeId;
3441
3804
  if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "") {
3442
3805
  return;
3443
3806
  }
3444
- const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, transaction);
3807
+ const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, options.transaction);
3445
3808
  await this.assertDesktopRouteBelongsToUiLayout(
3446
3809
  "applyBlueprint",
3447
3810
  groupRoute,
3448
3811
  layoutUid,
3449
3812
  "values.navigation.group.routeId",
3450
- transaction
3813
+ options.transaction
3451
3814
  );
3452
3815
  }
3453
- async resolveApplyBlueprintCreatePageIdentity(document, transaction) {
3454
- var _a, _b, _c, _d, _e, _f;
3816
+ async resolveApplyBlueprintCreatePageIdentity(document, options = {}) {
3817
+ var _a, _b, _c, _d, _e, _f, _g;
3455
3818
  if (document.mode !== "create") {
3456
3819
  return document;
3457
3820
  }
3458
3821
  const groupRouteId = (_b = (_a = document.navigation) == null ? void 0 : _a.group) == null ? void 0 : _b.routeId;
3459
3822
  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();
3823
+ let portalUid = this.navigationTargets.normalizePortalUid((_d = document.navigation) == null ? void 0 : _d.portalUid);
3824
+ 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
3825
  const hasGroupRouteId = !import_lodash.default.isNil(groupRouteId) && groupRouteId !== "";
3462
- if (!pageTitle || !hasGroupRouteId && !layoutUid) {
3826
+ if (!pageTitle || !hasGroupRouteId && !layoutUid && !portalUid) {
3463
3827
  return document;
3464
3828
  }
3465
- const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, transaction) : null;
3466
- const layoutUidFilter = layoutUid || (groupRoute ? await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, transaction) : void 0);
3829
+ const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, options.transaction) : null;
3830
+ const groupScope = groupRoute && !portalUid ? await this.readDesktopRouteScope(groupRoute, options.transaction) : void 0;
3831
+ if (!portalUid && groupRoute) {
3832
+ const groupPortalUids = (groupScope == null ? void 0 : groupScope.portalUids) || [];
3833
+ if (groupPortalUids.length > 1) {
3834
+ (0, import_errors.throwBadRequest)(
3835
+ `flowSurfaces applyBlueprint navigation.group.routeId '${groupRouteId}' belongs to multiple portals; pass navigation.portalUid explicitly`,
3836
+ {
3837
+ ruleId: "navigation-route-scope-ambiguous",
3838
+ path: "values.navigation.group.routeId",
3839
+ details: {
3840
+ routeId: groupRouteId,
3841
+ portalUids: groupPortalUids
3842
+ }
3843
+ }
3844
+ );
3845
+ }
3846
+ portalUid = groupPortalUids[0];
3847
+ }
3848
+ const portalMatchScope = portalUid ? await this.resolveDesktopRouteMatchScope(layoutUid, portalUid, "applyBlueprint", options) : void 0;
3849
+ let layoutUidFilter = (portalMatchScope == null ? void 0 : portalMatchScope.layoutUids) || layoutUid;
3850
+ if (!portalMatchScope && !layoutUid && groupRoute) {
3851
+ layoutUidFilter = (groupScope == null ? void 0 : groupScope.layoutUids.length) ? groupScope.layoutUids : await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, options.transaction);
3852
+ this.assertAdminLayoutCreateScope(
3853
+ "applyBlueprint",
3854
+ { portalUids: [], layoutUids: this.normalizeDesktopRouteLayoutUidFilter(layoutUidFilter) },
3855
+ "values.navigation.group.routeId"
3856
+ );
3857
+ }
3858
+ const portalUidFilter = portalMatchScope == null ? void 0 : portalMatchScope.portalUid;
3467
3859
  const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(
3468
3860
  hasGroupRouteId ? groupRouteId : null,
3469
3861
  pageTitle,
3470
- transaction,
3471
- layoutUidFilter
3862
+ options.transaction,
3863
+ layoutUidFilter,
3864
+ portalUidFilter
3472
3865
  );
3473
3866
  if (!matchedPages.length) {
3474
3867
  return document;
3475
3868
  }
3476
3869
  if (matchedPages.length > 1) {
3477
- const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3870
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3478
3871
  (0, import_errors.throwBadRequest)(
3479
3872
  `flowSurfaces applyBlueprint ${locationLabel} already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3480
3873
  );
3481
3874
  }
3482
3875
  const pageSchemaUid = String(this.readRouteField(matchedPages[0], "schemaUid") || "").trim();
3483
3876
  if (!pageSchemaUid) {
3484
- const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : `at the root of ui layout '${layoutUid}'`;
3877
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3485
3878
  (0, import_errors.throwBadRequest)(
3486
3879
  `flowSurfaces applyBlueprint existing flow page '${pageTitle}' ${locationLabel} is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3487
3880
  );
@@ -3495,21 +3888,19 @@ class FlowSurfacesService {
3495
3888
  navigation: void 0
3496
3889
  };
3497
3890
  }
3498
- async prepareApplyBlueprintRequest(values, transaction, createdKanbanSortFields) {
3891
+ async prepareApplyBlueprintRequest(values, options = {}, createdKanbanSortFields) {
3499
3892
  var _a;
3500
- const initialDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
3501
- await this.assertApplyBlueprintCreateNavigationLayout(initialDocument, transaction);
3502
- const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(
3503
- initialDocument,
3504
- transaction
3505
- );
3893
+ const parsedDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
3894
+ const initialDocument = await this.resolveApplyBlueprintDefaultNavigationTarget(parsedDocument, options);
3895
+ await this.assertApplyBlueprintCreateNavigationTarget(initialDocument, options);
3896
+ const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(initialDocument, options);
3506
3897
  const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(
3507
3898
  mobileNormalizedDocument,
3508
- transaction
3899
+ options
3509
3900
  );
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;
3901
+ const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, options);
3902
+ await this.prepareApplyBlueprintKanbanBlocks(document, options.transaction, createdKanbanSortFields);
3903
+ const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, options.transaction) : void 0;
3513
3904
  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
3905
  (0, import_errors.throwBadRequest)(
3515
3906
  `flowSurfaces applyBlueprint replace mode requires page.enableTabs=true when tabs.length > 1 and the current page has enableTabs=false`
@@ -4159,12 +4550,47 @@ class FlowSurfacesService {
4159
4550
  }
4160
4551
  }
4161
4552
  async assertApplyBlueprintAuthoringPayload(values, options = {}) {
4553
+ var _a, _b;
4162
4554
  const enabledPackages = await this.resolveEnabledPluginPackages(options);
4555
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = values == null ? void 0 : values.navigation) == null ? void 0 : _a.layoutUid);
4556
+ const portalUid = this.navigationTargets.normalizePortalUid((_b = values == null ? void 0 : values.navigation) == null ? void 0 : _b.portalUid);
4557
+ this.assertNavigationTargetIsExclusive("applyBlueprint", layoutUid, portalUid, {
4558
+ layout: "values.navigation.layoutUid",
4559
+ portal: "values.navigation.portalUid"
4560
+ });
4561
+ const resolvedPortal = portalUid ? await this.navigationTargets.resolvePortal(portalUid, {
4562
+ actionName: "applyBlueprint",
4563
+ path: "values.navigation.portalUid",
4564
+ currentRoles: options.currentRoles,
4565
+ transaction: options.transaction
4566
+ }) : void 0;
4163
4567
  await (0, import_authoring_validation.assertFlowSurfaceAuthoringPayload)("applyBlueprint", values, {
4164
4568
  transaction: options.transaction,
4165
4569
  enabledPackages,
4166
- findMenuGroupRoutesByTitle: (title, transaction, layoutUid) => this.findMenuGroupRoutesByTitle(title, transaction, layoutUid),
4167
- getUiLayoutTypeByUid: (layoutUid, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction),
4570
+ findMenuGroupRoutesByTitle: async (title, transaction, targetLayoutUid, targetPortalUid) => {
4571
+ const matchScope = await this.resolveDesktopRouteMatchScope(
4572
+ targetLayoutUid,
4573
+ targetPortalUid,
4574
+ "applyBlueprint",
4575
+ {
4576
+ transaction,
4577
+ currentRoles: options.currentRoles
4578
+ }
4579
+ );
4580
+ return this.findMenuGroupRoutesByTitle(title, transaction, matchScope.layoutUids, matchScope.portalUid);
4581
+ },
4582
+ getUiLayoutTypeByUid: (layoutUid2, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid2, transaction),
4583
+ getPortalLayoutTypeByUid: async (targetPortalUid, transaction) => {
4584
+ if ((resolvedPortal == null ? void 0 : resolvedPortal.uid) === targetPortalUid) {
4585
+ return resolvedPortal.layoutType;
4586
+ }
4587
+ return (await this.navigationTargets.resolvePortal(targetPortalUid, {
4588
+ actionName: "applyBlueprint",
4589
+ path: "values.navigation.portalUid",
4590
+ currentRoles: options.currentRoles,
4591
+ transaction
4592
+ })).layoutType;
4593
+ },
4168
4594
  getCollection: (dataSourceKey, collectionName) => this.getCollection(dataSourceKey || "main", collectionName || "")
4169
4595
  });
4170
4596
  }
@@ -4172,7 +4598,7 @@ class FlowSurfacesService {
4172
4598
  if (options.skipAuthoringValidation !== true) {
4173
4599
  await this.assertApplyBlueprintAuthoringPayload(values, options);
4174
4600
  }
4175
- const prepared = await this.prepareApplyBlueprintRequest(values, options.transaction, createdKanbanSortFields);
4601
+ const prepared = await this.prepareApplyBlueprintRequest(values, options, createdKanbanSortFields);
4176
4602
  const popupTemplateAliasSession = this.createPopupTemplateAliasSession();
4177
4603
  const popupTemplateTreeCache = /* @__PURE__ */ new Map();
4178
4604
  this.validateApplyBlueprintPopupTemplateAliases(prepared.document, popupTemplateAliasSession);
@@ -4803,6 +5229,7 @@ class FlowSurfacesService {
4803
5229
  );
4804
5230
  }
4805
5231
  const popupProfile = await this.resolvePopupBlockProfile(node.uid, null, node, transaction).catch(() => null);
5232
+ const sourceId = String(openView.sourceId || "").trim() || ((popupProfile == null ? void 0 : popupProfile.sourceIdInferred) ? "" : String((popupProfile == null ? void 0 : popupProfile.sourceId) || "").trim()) || void 0;
4806
5233
  const popupSourceNode = popupTargetUid ? await this.repository.findModelById(popupTargetUid, {
4807
5234
  transaction,
4808
5235
  includeAsyncNode: true
@@ -4816,7 +5243,7 @@ class FlowSurfacesService {
4816
5243
  collectionName: String(openView.collectionName || "").trim() || String((popupProfile == null ? void 0 : popupProfile.collectionName) || "").trim() || void 0,
4817
5244
  associationName: String(openView.associationName || "").trim() || String((popupProfile == null ? void 0 : popupProfile.associationName) || "").trim() || void 0,
4818
5245
  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,
5246
+ sourceId,
4820
5247
  openViewStep
4821
5248
  };
4822
5249
  }
@@ -6186,6 +6613,9 @@ class FlowSurfacesService {
6186
6613
  }
6187
6614
  (0, import_errors.throwBadRequest)(`flowSurfaces configure does not support configureOptions on '${(current == null ? void 0 : current.use) || resolved.uid}'`);
6188
6615
  }
6616
+ async listNavigationTargets(_values = {}, options = {}) {
6617
+ return this.navigationTargets.listNavigationTargets(options.currentRoles, options.transaction);
6618
+ }
6189
6619
  async createMenu(values, options = {}) {
6190
6620
  const publicValues = this.sanitizePublicCreateMenuValues(values || {});
6191
6621
  const type = publicValues.type === "group" ? "group" : "item";
@@ -6193,9 +6623,9 @@ class FlowSurfacesService {
6193
6623
  (0, import_errors.throwBadRequest)("flowSurfaces createMenu requires title");
6194
6624
  }
6195
6625
  if (type === "group") {
6196
- return this.createFlowMenuGroup(publicValues, options.transaction);
6626
+ return this.createFlowMenuGroup(publicValues, options);
6197
6627
  }
6198
- return this.createFlowMenuItem(publicValues, options.transaction);
6628
+ return this.createFlowMenuItem(publicValues, options);
6199
6629
  }
6200
6630
  async updateMenu(values, options = {}) {
6201
6631
  const menuRouteId = values.menuRouteId;
@@ -6205,6 +6635,53 @@ class FlowSurfacesService {
6205
6635
  const route = await this.assertMenuRouteUpdatable(menuRouteId, options.transaction);
6206
6636
  const nextParentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
6207
6637
  await this.assertMenuParentNotSelfOrDescendant(route, values.parentMenuRouteId, options.transaction);
6638
+ const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
6639
+ this.assertNavigationTargetIsExclusive("updateMenu", values.layoutUid, portalUid, {
6640
+ layout: "values.layoutUid",
6641
+ portal: "values.portalUid"
6642
+ });
6643
+ const routeScope = await this.readDesktopRouteScope(route, options.transaction);
6644
+ if (portalUid) {
6645
+ const portal = await this.navigationTargets.resolvePortal(portalUid, {
6646
+ actionName: "updateMenu",
6647
+ path: "values.portalUid",
6648
+ currentRoles: options.currentRoles,
6649
+ transaction: options.transaction
6650
+ });
6651
+ await this.assertRouteBelongsToResolvedPortal(
6652
+ "updateMenu",
6653
+ route,
6654
+ portal,
6655
+ "values.menuRouteId",
6656
+ options.transaction
6657
+ );
6658
+ if (nextParentRoute) {
6659
+ await this.assertRouteBelongsToResolvedPortal(
6660
+ "updateMenu",
6661
+ nextParentRoute,
6662
+ portal,
6663
+ "values.parentMenuRouteId",
6664
+ options.transaction
6665
+ );
6666
+ }
6667
+ }
6668
+ if (Object.prototype.hasOwnProperty.call(values, "parentMenuRouteId") && nextParentRoute) {
6669
+ const parentScope = await this.readDesktopRouteScope(nextParentRoute, options.transaction);
6670
+ const routePortalUids = [...routeScope.portalUids].sort();
6671
+ const parentPortalUids = [...parentScope.portalUids].sort();
6672
+ if (!import_lodash.default.isEqual(routePortalUids, parentPortalUids) && (routePortalUids.length || parentPortalUids.length)) {
6673
+ (0, import_errors.throwBadRequest)(`flowSurfaces updateMenu cannot move a menu route across portal scopes`, {
6674
+ ruleId: "navigation-route-portal-mismatch",
6675
+ path: "values.parentMenuRouteId",
6676
+ details: {
6677
+ routeId: this.readRouteField(route, "id"),
6678
+ routePortalUids,
6679
+ parentRouteId: this.readRouteField(nextParentRoute, "id"),
6680
+ parentPortalUids
6681
+ }
6682
+ });
6683
+ }
6684
+ }
6208
6685
  const nextOptions = { ...this.readRouteOptions(route) };
6209
6686
  const updateValues = {};
6210
6687
  if (Object.prototype.hasOwnProperty.call(values, "title")) {
@@ -6235,25 +6712,29 @@ class FlowSurfacesService {
6235
6712
  }
6236
6713
  const updated = await this.findMenuRouteById(menuRouteId, options.transaction);
6237
6714
  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
- );
6715
+ if (routeScope.portalUids.length) {
6716
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6717
+ this.readRouteField(updated, "id"),
6718
+ routeScope,
6719
+ options.transaction
6720
+ );
6721
+ } else {
6722
+ await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
6723
+ this.readRouteField(updated, "id"),
6724
+ await this.resolveInheritedDesktopRouteUiLayoutUids(nextParentRoute, options.transaction),
6725
+ options.transaction
6726
+ );
6727
+ }
6243
6728
  }
6244
6729
  return this.buildMenuResult(updated);
6245
6730
  }
6246
- async initializeFlowPageForRoute(route, values, transaction) {
6731
+ async initializeFlowPageForRoute(route, values, options = {}) {
6732
+ const transaction = options.transaction;
6247
6733
  const routeId = this.readRouteField(route, "id");
6248
6734
  const pageSchemaUid = this.readRouteField(route, "schemaUid");
6249
6735
  const structure = await this.loadRouteBackedPageStructure(route, transaction);
6250
6736
  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);
6737
+ const routeScope = await this.resolveExistingDesktopRouteScope(values, route, "createPage", options);
6257
6738
  const routeOptions = this.readRouteOptions(route);
6258
6739
  if (!routeOptions[FLOW_SURFACE_MENU_BINDABLE_OPTION_KEY]) {
6259
6740
  (0, import_errors.throwBadRequest)(`flowSurfaces createPage only accepts a bindable menu route created by createMenu(type="item")`);
@@ -6292,7 +6773,11 @@ class FlowSurfacesService {
6292
6773
  },
6293
6774
  transaction
6294
6775
  });
6295
- await this.setDesktopRouteUiLayouts(this.readRouteField(tabRoute, "id"), routeUiLayoutUids, transaction);
6776
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6777
+ this.readRouteField(tabRoute, "id"),
6778
+ routeScope,
6779
+ transaction
6780
+ );
6296
6781
  } else {
6297
6782
  await this.db.getRepository("desktopRoutes").update({
6298
6783
  filterByTk: String(this.readRouteField(tabRoute, "id")),
@@ -6317,7 +6802,11 @@ class FlowSurfacesService {
6317
6802
  });
6318
6803
  }
6319
6804
  if (tabRoute) {
6320
- await this.setDesktopRouteUiLayouts(this.readRouteField(tabRoute, "id"), routeUiLayoutUids, transaction);
6805
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6806
+ this.readRouteField(tabRoute, "id"),
6807
+ routeScope,
6808
+ transaction
6809
+ );
6321
6810
  }
6322
6811
  await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, transaction);
6323
6812
  await this.db.getRepository("desktopRoutes").update({
@@ -6369,11 +6858,11 @@ class FlowSurfacesService {
6369
6858
  let result;
6370
6859
  if (!import_lodash.default.isNil(values.menuRouteId) && values.menuRouteId !== "") {
6371
6860
  const route = await this.assertMenuRouteBindable(values.menuRouteId, options.transaction);
6372
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6861
+ result = await this.initializeFlowPageForRoute(route, values, options);
6373
6862
  } else {
6374
- const createdMenu = await this.createFlowMenuItem(values, options.transaction);
6863
+ const createdMenu = await this.createFlowMenuItem(values, options);
6375
6864
  const route = await this.assertMenuRouteBindable(createdMenu.routeId, options.transaction);
6376
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6865
+ result = await this.initializeFlowPageForRoute(route, values, options);
6377
6866
  }
6378
6867
  await this.persistCreatedKeysForAction("createPage", values, result, options.transaction);
6379
6868
  return result;
@@ -6458,11 +6947,11 @@ class FlowSurfacesService {
6458
6947
  },
6459
6948
  transaction: options.transaction
6460
6949
  });
6461
- await this.setDesktopRouteUiLayouts(
6462
- route.get("id"),
6463
- await this.ensureDesktopRouteUiLayouts(pageRoute, options.transaction),
6464
- options.transaction
6465
- );
6950
+ const pageRouteScope = await this.readDesktopRouteScope(pageRoute, options.transaction);
6951
+ if (!pageRouteScope.portalUids.length && !pageRouteScope.layoutUids.length) {
6952
+ pageRouteScope.layoutUids = await this.ensureDesktopRouteUiLayouts(pageRoute, options.transaction);
6953
+ }
6954
+ await this.attachDesktopRouteScopeToRouteAndChildren(route.get("id"), pageRouteScope, options.transaction);
6466
6955
  const gridUid = await this.ensureGridChild(tabSchemaUid, "BlockGridModel", options.transaction);
6467
6956
  const result = {
6468
6957
  pageUid: pageTarget.uid,