@nocobase/plugin-flow-engine 2.1.31 → 2.2.0-alpha.10

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 (45) 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 +3 -1
  17. package/dist/server/flow-surfaces/authoring-validation.js +30 -10
  18. package/dist/server/flow-surfaces/blueprint/compile-plan.js +23 -15
  19. package/dist/server/flow-surfaces/blueprint/normalize-document.js +18 -1
  20. package/dist/server/flow-surfaces/blueprint/public-types.d.ts +2 -0
  21. package/dist/server/flow-surfaces/builder.js +24 -4
  22. package/dist/server/flow-surfaces/constants.d.ts +9 -2
  23. package/dist/server/flow-surfaces/constants.js +1 -0
  24. package/dist/server/flow-surfaces/errors.d.ts +2 -2
  25. package/dist/server/flow-surfaces/errors.js +4 -4
  26. package/dist/server/flow-surfaces/navigation-targets.d.ts +73 -0
  27. package/dist/server/flow-surfaces/navigation-targets.js +439 -0
  28. package/dist/server/flow-surfaces/runjs-authoring/rules.js +0 -2
  29. package/dist/server/flow-surfaces/runjs-authoring/runtime/constants.d.ts +0 -1
  30. package/dist/server/flow-surfaces/runjs-authoring/runtime/constants.js +12 -19
  31. package/dist/server/flow-surfaces/runjs-authoring/runtime/types.d.ts +0 -12
  32. package/dist/server/flow-surfaces/runjs-authoring/scan/index.d.ts +0 -14
  33. package/dist/server/flow-surfaces/runjs-authoring/scan/index.js +1 -7
  34. package/dist/server/flow-surfaces/runjs-authoring/scan/source-patterns.d.ts +0 -14
  35. package/dist/server/flow-surfaces/runjs-authoring/scan/source-patterns.js +18 -73
  36. package/dist/server/flow-surfaces/runjs-authoring/types.d.ts +1 -1
  37. package/dist/server/flow-surfaces/runjs-authoring/validators/index.js +9 -53
  38. package/dist/server/flow-surfaces/service.d.ts +31 -0
  39. package/dist/server/flow-surfaces/service.js +749 -53
  40. package/dist/swagger/flow-surfaces.d.ts +108 -0
  41. package/dist/swagger/flow-surfaces.js +101 -8
  42. package/dist/swagger/index.d.ts +108 -0
  43. package/package.json +2 -2
  44. package/dist/server/flow-surfaces/runjs-authoring/blocked-global-stop/index.d.ts +0 -10
  45. package/dist/server/flow-surfaces/runjs-authoring/blocked-global-stop/index.js +0 -40
@@ -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,6 +690,7 @@ function assertNoFlowSurfaceIdTitleFieldSettings(value, options = {}) {
689
690
  };
690
691
  visit(value, options.basePath || []);
691
692
  }
693
+ const MOBILE_UI_LAYOUT_TYPE = "mobile";
692
694
  class FlowSurfacesService {
693
695
  constructor(plugin) {
694
696
  this.plugin = plugin;
@@ -709,6 +711,9 @@ class FlowSurfacesService {
709
711
  (values, options) => this.patchFlowSurfaceModelOptions(values, options)
710
712
  );
711
713
  }
714
+ get navigationTargets() {
715
+ return new import_navigation_targets.FlowSurfaceNavigationTargetsService(this.db);
716
+ }
712
717
  getFlowTemplateRepository(actionName) {
713
718
  try {
714
719
  const repo = this.db.getRepository("flowModelTemplates");
@@ -922,12 +927,12 @@ class FlowSurfacesService {
922
927
  transaction
923
928
  });
924
929
  }
925
- async findMenuGroupRoutesByTitle(title, transaction) {
930
+ async findMenuGroupRoutesByTitle(title, transaction, layoutUid, portalUid) {
926
931
  const normalizedTitle = String(title || "").trim();
927
932
  if (!normalizedTitle) {
928
933
  return [];
929
934
  }
930
- return import_lodash.default.castArray(
935
+ const routes = import_lodash.default.castArray(
931
936
  await this.db.getRepository("desktopRoutes").find({
932
937
  filter: {
933
938
  type: "group",
@@ -936,6 +941,32 @@ class FlowSurfacesService {
936
941
  transaction
937
942
  })
938
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
+ }
958
+ const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
959
+ if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
960
+ return routes;
961
+ }
962
+ const matchedRoutes = [];
963
+ for (const route of routes) {
964
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(this.readRouteField(route, "id"), transaction);
965
+ if (import_lodash.default.intersection(routeLayoutUids, layoutUidFilter).length) {
966
+ matchedRoutes.push(route);
967
+ }
968
+ }
969
+ return matchedRoutes;
939
970
  }
940
971
  routeParentIdMatches(routeParentId, parentId) {
941
972
  if (import_lodash.default.isNil(routeParentId) && import_lodash.default.isNil(parentId)) {
@@ -943,28 +974,58 @@ class FlowSurfacesService {
943
974
  }
944
975
  return String(routeParentId ?? "") === String(parentId ?? "");
945
976
  }
946
- async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction) {
947
- const routes = await this.findMenuGroupRoutesByTitle(title, transaction);
977
+ async findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid, portalUid) {
978
+ const routes = await this.findMenuGroupRoutesByTitle(title, transaction, layoutUid, portalUid);
948
979
  return routes.filter(
949
980
  (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
950
981
  );
951
982
  }
952
- async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction) {
983
+ async findFlowPageRoutesByParentIdAndTitle(parentId, title, transaction, layoutUid, portalUid) {
953
984
  const normalizedTitle = String(title || "").trim();
954
- const normalizedParentId = String(parentId ?? "").trim();
955
- if (!normalizedTitle || !normalizedParentId) {
985
+ if (!normalizedTitle) {
956
986
  return [];
957
987
  }
958
- return import_lodash.default.castArray(
988
+ const routes = import_lodash.default.castArray(
959
989
  await this.db.getRepository("desktopRoutes").find({
960
- filter: {
990
+ filter: (0, import_service_utils.buildDefinedPayload)({
961
991
  type: "flowPage",
962
992
  title: normalizedTitle,
963
- parentId: normalizedParentId
964
- },
993
+ parentId: import_lodash.default.isNil(parentId) ? void 0 : String(parentId)
994
+ }),
965
995
  transaction
966
996
  })
967
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
+ }
1014
+ const layoutUidFilter = await this.resolveDesktopRouteLayoutUidFilter(layoutUid, transaction);
1015
+ const layoutMatchedRoutes = [];
1016
+ if (!layoutUidFilter.length || !this.hasDesktopRouteUiLayoutsRelation()) {
1017
+ layoutMatchedRoutes.push(...routes);
1018
+ } else {
1019
+ for (const route of routes) {
1020
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(this.readRouteField(route, "id"), transaction);
1021
+ if (import_lodash.default.intersection(routeLayoutUids, layoutUidFilter).length) {
1022
+ layoutMatchedRoutes.push(route);
1023
+ }
1024
+ }
1025
+ }
1026
+ return layoutMatchedRoutes.filter(
1027
+ (route) => this.routeParentIdMatches(this.readRouteField(route, "parentId") ?? null, parentId)
1028
+ );
968
1029
  }
969
1030
  async assertMenuParentIsGroup(parentMenuRouteId, transaction) {
970
1031
  if (import_lodash.default.isNil(parentMenuRouteId)) {
@@ -1030,6 +1091,380 @@ class FlowSurfacesService {
1030
1091
  ...extras
1031
1092
  };
1032
1093
  }
1094
+ hasDesktopRouteUiLayoutsRelation() {
1095
+ var _a, _b;
1096
+ try {
1097
+ return !!this.db.getCollection("uiLayouts") && !!((_b = (_a = this.db.getCollection("desktopRoutes")) == null ? void 0 : _a.getField) == null ? void 0 : _b.call(_a, "uiLayouts"));
1098
+ } catch (error) {
1099
+ return false;
1100
+ }
1101
+ }
1102
+ normalizeDesktopRouteRelationId(routeId) {
1103
+ return import_lodash.default.isNil(routeId) ? void 0 : routeId;
1104
+ }
1105
+ async readDesktopRouteUiLayoutUids(routeId, transaction) {
1106
+ const relationRouteId = this.normalizeDesktopRouteRelationId(routeId);
1107
+ if (import_lodash.default.isUndefined(relationRouteId) || !this.hasDesktopRouteUiLayoutsRelation()) {
1108
+ return [];
1109
+ }
1110
+ const uiLayouts = await this.db.getRepository("desktopRoutes.uiLayouts", relationRouteId).find({
1111
+ fields: ["uid"],
1112
+ transaction
1113
+ });
1114
+ return import_lodash.default.uniq(
1115
+ uiLayouts.map((uiLayout) => {
1116
+ var _a;
1117
+ return String(((_a = uiLayout == null ? void 0 : uiLayout.get) == null ? void 0 : _a.call(uiLayout, "uid")) || (uiLayout == null ? void 0 : uiLayout.uid) || "").trim();
1118
+ }).filter(Boolean)
1119
+ );
1120
+ }
1121
+ async resolveDefaultDesktopRouteUiLayoutUids(transaction) {
1122
+ if (!this.hasDesktopRouteUiLayoutsRelation()) {
1123
+ return [];
1124
+ }
1125
+ const defaultLayout = await this.db.getRepository("uiLayouts").findOne({
1126
+ filter: {
1127
+ uid: import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID,
1128
+ enabled: true
1129
+ },
1130
+ transaction
1131
+ });
1132
+ return defaultLayout ? [import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID] : [];
1133
+ }
1134
+ async findDesktopRouteUiLayoutByUid(layoutUid, transaction) {
1135
+ const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
1136
+ if (!normalizedLayoutUid || !this.hasDesktopRouteUiLayoutsRelation()) {
1137
+ return null;
1138
+ }
1139
+ return this.db.getRepository("uiLayouts").findOne({
1140
+ filter: {
1141
+ uid: normalizedLayoutUid
1142
+ },
1143
+ transaction
1144
+ });
1145
+ }
1146
+ async getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction) {
1147
+ var _a;
1148
+ const uiLayout = await this.findDesktopRouteUiLayoutByUid(layoutUid, transaction);
1149
+ return String(((_a = uiLayout == null ? void 0 : uiLayout.get) == null ? void 0 : _a.call(uiLayout, "layoutType")) || (uiLayout == null ? void 0 : uiLayout.layoutType) || "").trim() || void 0;
1150
+ }
1151
+ async isMobileDesktopRouteUiLayoutUid(layoutUid, transaction) {
1152
+ if (!layoutUid) {
1153
+ return false;
1154
+ }
1155
+ return await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, transaction) === MOBILE_UI_LAYOUT_TYPE;
1156
+ }
1157
+ async resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction) {
1158
+ const parentRouteId = this.readRouteField(parentRoute, "id");
1159
+ if (!import_lodash.default.isNil(parentRouteId)) {
1160
+ const parentLayoutUids = await this.readDesktopRouteUiLayoutUids(parentRouteId, transaction);
1161
+ if (parentLayoutUids.length) {
1162
+ return parentLayoutUids;
1163
+ }
1164
+ }
1165
+ return this.resolveDefaultDesktopRouteUiLayoutUids(transaction);
1166
+ }
1167
+ normalizeExplicitDesktopRouteLayoutUid(value) {
1168
+ const normalized = String(value || "").trim();
1169
+ return normalized || void 0;
1170
+ }
1171
+ normalizeDesktopRouteLayoutUidFilter(value) {
1172
+ return import_lodash.default.uniq(
1173
+ import_lodash.default.castArray(value).map((item) => this.normalizeExplicitDesktopRouteLayoutUid(item)).filter(Boolean)
1174
+ );
1175
+ }
1176
+ async resolveDesktopRouteLayoutUidFilter(value, transaction) {
1177
+ const explicitLayoutUids = this.normalizeDesktopRouteLayoutUidFilter(value);
1178
+ if (explicitLayoutUids.length) {
1179
+ return explicitLayoutUids;
1180
+ }
1181
+ return this.resolveDefaultDesktopRouteUiLayoutUids(transaction);
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
+ }
1376
+ async assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, path, transaction) {
1377
+ if (!this.hasDesktopRouteUiLayoutsRelation()) {
1378
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} requires plugin-ui-layout desktop route relation support`, {
1379
+ ruleId: "navigation-layout-unsupported",
1380
+ path,
1381
+ details: {
1382
+ layoutUid
1383
+ }
1384
+ });
1385
+ }
1386
+ const uiLayout = await this.db.getRepository("uiLayouts").findOne({
1387
+ filter: {
1388
+ uid: layoutUid,
1389
+ enabled: true
1390
+ },
1391
+ transaction
1392
+ });
1393
+ if (!uiLayout) {
1394
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} must reference an enabled ui layout`, {
1395
+ ruleId: "navigation-layout-not-found",
1396
+ path,
1397
+ details: {
1398
+ layoutUid
1399
+ }
1400
+ });
1401
+ }
1402
+ }
1403
+ async assertDesktopRouteBelongsToUiLayout(actionName, route, layoutUid, path, transaction) {
1404
+ const normalizedLayoutUid = this.normalizeExplicitDesktopRouteLayoutUid(layoutUid);
1405
+ if (!normalizedLayoutUid || !route) {
1406
+ return;
1407
+ }
1408
+ const routeId = this.readRouteField(route, "id");
1409
+ const routeLayoutUids = await this.readDesktopRouteUiLayoutUids(routeId, transaction);
1410
+ if (routeLayoutUids.includes(normalizedLayoutUid)) {
1411
+ return;
1412
+ }
1413
+ (0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} ${path} does not belong to ui layout '${normalizedLayoutUid}'`, {
1414
+ ruleId: "navigation-route-layout-mismatch",
1415
+ path,
1416
+ details: {
1417
+ routeId,
1418
+ layoutUid: normalizedLayoutUid,
1419
+ routeLayoutUids
1420
+ }
1421
+ });
1422
+ }
1423
+ async setDesktopRouteUiLayouts(routeId, uiLayoutUids, transaction) {
1424
+ const relationRouteId = this.normalizeDesktopRouteRelationId(routeId);
1425
+ if (import_lodash.default.isUndefined(relationRouteId) || !uiLayoutUids.length || !this.hasDesktopRouteUiLayoutsRelation()) {
1426
+ return;
1427
+ }
1428
+ await this.db.getRepository("desktopRoutes.uiLayouts", relationRouteId).set({
1429
+ tk: uiLayoutUids,
1430
+ transaction
1431
+ });
1432
+ }
1433
+ async attachDesktopRouteUiLayoutsToRouteAndChildren(routeId, uiLayoutUids, transaction) {
1434
+ if (import_lodash.default.isNil(routeId) || !uiLayoutUids.length || !this.hasDesktopRouteUiLayoutsRelation()) {
1435
+ return;
1436
+ }
1437
+ await this.setDesktopRouteUiLayouts(routeId, uiLayoutUids, transaction);
1438
+ const children = await this.db.getRepository("desktopRoutes").find({
1439
+ fields: ["id"],
1440
+ filter: {
1441
+ parentId: routeId
1442
+ },
1443
+ transaction
1444
+ });
1445
+ for (const child of children) {
1446
+ await this.attachDesktopRouteUiLayoutsToRouteAndChildren(
1447
+ this.readRouteField(child, "id"),
1448
+ uiLayoutUids,
1449
+ transaction
1450
+ );
1451
+ }
1452
+ }
1453
+ async ensureDesktopRouteUiLayouts(route, transaction) {
1454
+ const routeId = this.readRouteField(route, "id");
1455
+ if (import_lodash.default.isNil(routeId) || !this.hasDesktopRouteUiLayoutsRelation()) {
1456
+ return [];
1457
+ }
1458
+ const existingLayoutUids = await this.readDesktopRouteUiLayoutUids(routeId, transaction);
1459
+ if (existingLayoutUids.length) {
1460
+ return existingLayoutUids;
1461
+ }
1462
+ const parentRouteId = this.readRouteField(route, "parentId");
1463
+ const parentRoute = import_lodash.default.isNil(parentRouteId) ? null : await this.findMenuRouteById(parentRouteId, transaction);
1464
+ const inheritedLayoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, transaction);
1465
+ await this.setDesktopRouteUiLayouts(routeId, inheritedLayoutUids, transaction);
1466
+ return inheritedLayoutUids;
1467
+ }
1033
1468
  isValidMenuIconName(value) {
1034
1469
  const normalized = String(value || "").trim();
1035
1470
  return !!normalized && ANT_DESIGN_ICON_NAMES.has(normalized);
@@ -1082,11 +1517,19 @@ class FlowSurfacesService {
1082
1517
  });
1083
1518
  }
1084
1519
  }
1085
- async createFlowMenuGroup(values, transaction) {
1086
- 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);
1087
1523
  const parentId = this.readRouteField(parentRoute, "id") ?? null;
1524
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1088
1525
  const title = String(values.title || "").trim();
1089
- const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(parentId, title, transaction);
1526
+ const existingGroups = await this.findMenuGroupRoutesByParentIdAndTitle(
1527
+ parentId,
1528
+ title,
1529
+ options.transaction,
1530
+ routeScope.layoutUids,
1531
+ (_a = routeScope.selectedPortal) == null ? void 0 : _a.uid
1532
+ );
1090
1533
  if (existingGroups.length === 1) {
1091
1534
  return this.buildMenuResult(existingGroups[0]);
1092
1535
  }
@@ -1111,7 +1554,7 @@ class FlowSurfacesService {
1111
1554
  this.assertVisibleNavigationIcon("createMenu", "values", values);
1112
1555
  const schemaUid = values.schemaUid || (0, import_utils.uid)();
1113
1556
  const desktopRoutes = this.db.getRepository("desktopRoutes");
1114
- await desktopRoutes.create({
1557
+ const route = await desktopRoutes.create({
1115
1558
  values: {
1116
1559
  type: "group",
1117
1560
  sort: this.allocateRouteSortValue(),
@@ -1122,17 +1565,19 @@ class FlowSurfacesService {
1122
1565
  hideInMenu: !!values.hideInMenu,
1123
1566
  parentId
1124
1567
  },
1125
- transaction
1126
- });
1127
- const route = await desktopRoutes.findOne({
1128
- filter: { schemaUid },
1129
- transaction
1568
+ transaction: options.transaction
1130
1569
  });
1570
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1571
+ this.readRouteField(route, "id"),
1572
+ routeScope,
1573
+ options.transaction
1574
+ );
1131
1575
  return this.buildMenuResult(route);
1132
1576
  }
1133
- async createFlowMenuItem(values, transaction) {
1577
+ async createFlowMenuItem(values, options = {}) {
1134
1578
  var _a;
1135
- const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, transaction);
1579
+ const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
1580
+ const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
1136
1581
  this.assertVisibleNavigationIcon("createMenu", "values", values);
1137
1582
  const pageSchemaUid = values.pageSchemaUid || (0, import_utils.uid)();
1138
1583
  const menuSchemaUid = (0, import_utils.uid)();
@@ -1142,7 +1587,7 @@ class FlowSurfacesService {
1142
1587
  const title = values.title || pageSchemaUid;
1143
1588
  const tabTitle = values.tabTitle || "Untitled";
1144
1589
  const desktopRoutes = this.db.getRepository("desktopRoutes");
1145
- await desktopRoutes.create({
1590
+ const createdRoute = await desktopRoutes.create({
1146
1591
  values: {
1147
1592
  type: "flowPage",
1148
1593
  sort: this.allocateRouteSortValue(),
@@ -1175,15 +1620,20 @@ class FlowSurfacesService {
1175
1620
  }
1176
1621
  ]
1177
1622
  },
1178
- transaction
1623
+ transaction: options.transaction
1179
1624
  });
1625
+ await this.attachDesktopRouteScopeToRouteAndChildren(
1626
+ this.readRouteField(createdRoute, "id"),
1627
+ routeScope,
1628
+ options.transaction
1629
+ );
1180
1630
  const route = await desktopRoutes.findOne({
1181
1631
  filter: { schemaUid: pageSchemaUid },
1182
1632
  appends: ["children"],
1183
- transaction
1633
+ transaction: options.transaction
1184
1634
  });
1185
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];
1186
- await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, transaction);
1636
+ await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, options.transaction);
1187
1637
  const pageTree = (0, import_builder.buildPersistedRootPageModel)({
1188
1638
  pageUid,
1189
1639
  pageTitle: title,
@@ -1199,7 +1649,7 @@ class FlowSurfacesService {
1199
1649
  subType: "object",
1200
1650
  ...pageTree
1201
1651
  },
1202
- { transaction }
1652
+ { transaction: options.transaction }
1203
1653
  );
1204
1654
  return this.buildMenuResult(route, {
1205
1655
  pageSchemaUid,
@@ -1211,7 +1661,16 @@ class FlowSurfacesService {
1211
1661
  });
1212
1662
  }
1213
1663
  sanitizePublicCreateMenuValues(values) {
1214
- return import_lodash.default.pick(values, ["title", "type", "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
+ ]);
1215
1674
  }
1216
1675
  async loadRouteBackedPageStructure(route, transaction) {
1217
1676
  const routeId = this.readRouteField(route, "id");
@@ -3122,7 +3581,15 @@ class FlowSurfacesService {
3122
3581
  if (!groupTitle) {
3123
3582
  return document;
3124
3583
  }
3125
- const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(null, groupTitle, transaction);
3584
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(document.navigation.layoutUid);
3585
+ const portalUid = this.navigationTargets.normalizePortalUid(document.navigation.portalUid);
3586
+ const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(
3587
+ null,
3588
+ groupTitle,
3589
+ transaction,
3590
+ layoutUid || await this.resolveDefaultDesktopRouteUiLayoutUids(transaction),
3591
+ portalUid
3592
+ );
3126
3593
  if (!matchedRoutes.length) {
3127
3594
  return document;
3128
3595
  }
@@ -3147,29 +3614,148 @@ class FlowSurfacesService {
3147
3614
  }
3148
3615
  };
3149
3616
  }
3617
+ async normalizeApplyBlueprintCreateMobileNavigation(document, options = {}) {
3618
+ var _a, _b;
3619
+ if (document.mode !== "create") {
3620
+ return document;
3621
+ }
3622
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.layoutUid);
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) {
3631
+ return document;
3632
+ }
3633
+ return {
3634
+ ...document,
3635
+ navigation: (0, import_service_utils.buildDefinedPayload)({
3636
+ ...document.navigation,
3637
+ layoutUid,
3638
+ portalUid,
3639
+ group: void 0
3640
+ })
3641
+ };
3642
+ }
3643
+ async assertApplyBlueprintCreateNavigationTarget(document, options = {}) {
3644
+ var _a, _b, _c, _d, _e, _f;
3645
+ if (document.mode !== "create") {
3646
+ return;
3647
+ }
3648
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_a = document.navigation) == null ? void 0 : _a.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
+ );
3679
+ return;
3680
+ }
3681
+ await this.assertEnabledDesktopRouteUiLayoutUid(
3682
+ layoutUid,
3683
+ "applyBlueprint",
3684
+ "values.navigation.layoutUid",
3685
+ options.transaction
3686
+ );
3687
+ if (await this.isMobileDesktopRouteUiLayoutUid(layoutUid, options.transaction)) {
3688
+ return;
3689
+ }
3690
+ const groupRouteId = (_f = (_e = document.navigation) == null ? void 0 : _e.group) == null ? void 0 : _f.routeId;
3691
+ if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "") {
3692
+ return;
3693
+ }
3694
+ const groupRoute = await this.assertMenuParentIsGroup(groupRouteId, options.transaction);
3695
+ await this.assertDesktopRouteBelongsToUiLayout(
3696
+ "applyBlueprint",
3697
+ groupRoute,
3698
+ layoutUid,
3699
+ "values.navigation.group.routeId",
3700
+ options.transaction
3701
+ );
3702
+ }
3150
3703
  async resolveApplyBlueprintCreatePageIdentity(document, transaction) {
3151
- var _a, _b, _c, _d, _e;
3704
+ var _a, _b, _c, _d, _e, _f, _g;
3152
3705
  if (document.mode !== "create") {
3153
3706
  return document;
3154
3707
  }
3155
3708
  const groupRouteId = (_b = (_a = document.navigation) == null ? void 0 : _a.group) == null ? void 0 : _b.routeId;
3156
- const pageTitle = String(((_c = document.page) == null ? void 0 : _c.title) || ((_e = (_d = document.navigation) == null ? void 0 : _d.item) == null ? void 0 : _e.title) || "").trim();
3157
- if (import_lodash.default.isNil(groupRouteId) || groupRouteId === "" || !pageTitle) {
3709
+ const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid((_c = document.navigation) == null ? void 0 : _c.layoutUid);
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();
3712
+ const hasGroupRouteId = !import_lodash.default.isNil(groupRouteId) && groupRouteId !== "";
3713
+ if (!pageTitle || !hasGroupRouteId && !layoutUid && !portalUid) {
3158
3714
  return document;
3159
3715
  }
3160
- const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(groupRouteId, pageTitle, transaction);
3716
+ const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, transaction) : null;
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);
3738
+ const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(
3739
+ hasGroupRouteId ? groupRouteId : null,
3740
+ pageTitle,
3741
+ transaction,
3742
+ layoutUidFilter,
3743
+ portalUid
3744
+ );
3161
3745
  if (!matchedPages.length) {
3162
3746
  return document;
3163
3747
  }
3164
3748
  if (matchedPages.length > 1) {
3749
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3165
3750
  (0, import_errors.throwBadRequest)(
3166
- `flowSurfaces applyBlueprint navigation.group.routeId '${groupRouteId}' already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3751
+ `flowSurfaces applyBlueprint ${locationLabel} already has ${matchedPages.length} flow pages titled '${pageTitle}'; pass target.pageSchemaUid explicitly before applyBlueprint`
3167
3752
  );
3168
3753
  }
3169
3754
  const pageSchemaUid = String(this.readRouteField(matchedPages[0], "schemaUid") || "").trim();
3170
3755
  if (!pageSchemaUid) {
3756
+ const locationLabel = hasGroupRouteId ? `under navigation.group.routeId '${groupRouteId}'` : portalUid ? `at the root of portal '${portalUid}'` : `at the root of ui layout '${layoutUid}'`;
3171
3757
  (0, import_errors.throwBadRequest)(
3172
- `flowSurfaces applyBlueprint existing flow page '${pageTitle}' under navigation.group.routeId '${groupRouteId}' is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3758
+ `flowSurfaces applyBlueprint existing flow page '${pageTitle}' ${locationLabel} is missing schemaUid; pass target.pageSchemaUid explicitly before applyBlueprint`
3173
3759
  );
3174
3760
  }
3175
3761
  return {
@@ -3181,13 +3767,18 @@ class FlowSurfacesService {
3181
3767
  navigation: void 0
3182
3768
  };
3183
3769
  }
3184
- async prepareApplyBlueprintRequest(values, transaction, createdKanbanSortFields) {
3770
+ async prepareApplyBlueprintRequest(values, options = {}, createdKanbanSortFields) {
3185
3771
  var _a;
3186
3772
  const initialDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
3187
- const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(initialDocument, transaction);
3188
- const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, transaction);
3189
- await this.prepareApplyBlueprintKanbanBlocks(document, transaction, createdKanbanSortFields);
3190
- const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, transaction) : void 0;
3773
+ await this.assertApplyBlueprintCreateNavigationTarget(initialDocument, options);
3774
+ const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(initialDocument, options);
3775
+ const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(
3776
+ mobileNormalizedDocument,
3777
+ options.transaction
3778
+ );
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;
3191
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) {
3192
3783
  (0, import_errors.throwBadRequest)(
3193
3784
  `flowSurfaces applyBlueprint replace mode requires page.enableTabs=true when tabs.length > 1 and the current page has enableTabs=false`
@@ -3837,11 +4428,36 @@ class FlowSurfacesService {
3837
4428
  }
3838
4429
  }
3839
4430
  async assertApplyBlueprintAuthoringPayload(values, options = {}) {
4431
+ var _a, _b;
3840
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;
3841
4445
  await (0, import_authoring_validation.assertFlowSurfaceAuthoringPayload)("applyBlueprint", values, {
3842
4446
  transaction: options.transaction,
3843
4447
  enabledPackages,
3844
- findMenuGroupRoutesByTitle: (title, transaction) => this.findMenuGroupRoutesByTitle(title, 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
+ },
3845
4461
  getCollection: (dataSourceKey, collectionName) => this.getCollection(dataSourceKey || "main", collectionName || "")
3846
4462
  });
3847
4463
  }
@@ -3849,7 +4465,7 @@ class FlowSurfacesService {
3849
4465
  if (options.skipAuthoringValidation !== true) {
3850
4466
  await this.assertApplyBlueprintAuthoringPayload(values, options);
3851
4467
  }
3852
- const prepared = await this.prepareApplyBlueprintRequest(values, options.transaction, createdKanbanSortFields);
4468
+ const prepared = await this.prepareApplyBlueprintRequest(values, options, createdKanbanSortFields);
3853
4469
  const popupTemplateAliasSession = this.createPopupTemplateAliasSession();
3854
4470
  const popupTemplateTreeCache = /* @__PURE__ */ new Map();
3855
4471
  this.validateApplyBlueprintPopupTemplateAliases(prepared.document, popupTemplateAliasSession);
@@ -5864,6 +6480,9 @@ class FlowSurfacesService {
5864
6480
  }
5865
6481
  (0, import_errors.throwBadRequest)(`flowSurfaces configure does not support configureOptions on '${(current == null ? void 0 : current.use) || resolved.uid}'`);
5866
6482
  }
6483
+ async listNavigationTargets(_values = {}, options = {}) {
6484
+ return this.navigationTargets.listNavigationTargets(options.currentRoles, options.transaction);
6485
+ }
5867
6486
  async createMenu(values, options = {}) {
5868
6487
  const publicValues = this.sanitizePublicCreateMenuValues(values || {});
5869
6488
  const type = publicValues.type === "group" ? "group" : "item";
@@ -5871,9 +6490,9 @@ class FlowSurfacesService {
5871
6490
  (0, import_errors.throwBadRequest)("flowSurfaces createMenu requires title");
5872
6491
  }
5873
6492
  if (type === "group") {
5874
- return this.createFlowMenuGroup(publicValues, options.transaction);
6493
+ return this.createFlowMenuGroup(publicValues, options);
5875
6494
  }
5876
- return this.createFlowMenuItem(publicValues, options.transaction);
6495
+ return this.createFlowMenuItem(publicValues, options);
5877
6496
  }
5878
6497
  async updateMenu(values, options = {}) {
5879
6498
  const menuRouteId = values.menuRouteId;
@@ -5881,8 +6500,55 @@ class FlowSurfacesService {
5881
6500
  (0, import_errors.throwBadRequest)("flowSurfaces updateMenu requires menuRouteId");
5882
6501
  }
5883
6502
  const route = await this.assertMenuRouteUpdatable(menuRouteId, options.transaction);
5884
- await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
6503
+ const nextParentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
5885
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
+ }
5886
6552
  const nextOptions = { ...this.readRouteOptions(route) };
5887
6553
  const updateValues = {};
5888
6554
  if (Object.prototype.hasOwnProperty.call(values, "title")) {
@@ -5912,13 +6578,30 @@ class FlowSurfacesService {
5912
6578
  });
5913
6579
  }
5914
6580
  const updated = await this.findMenuRouteById(menuRouteId, options.transaction);
6581
+ if (updated && Object.prototype.hasOwnProperty.call(values, "parentMenuRouteId")) {
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
+ }
6595
+ }
5915
6596
  return this.buildMenuResult(updated);
5916
6597
  }
5917
- async initializeFlowPageForRoute(route, values, transaction) {
6598
+ async initializeFlowPageForRoute(route, values, options = {}) {
6599
+ const transaction = options.transaction;
5918
6600
  const routeId = this.readRouteField(route, "id");
5919
6601
  const pageSchemaUid = this.readRouteField(route, "schemaUid");
5920
6602
  const structure = await this.loadRouteBackedPageStructure(route, transaction);
5921
6603
  let tabRoute = structure.tabRoutes[0];
6604
+ const routeScope = await this.resolveExistingDesktopRouteScope(values, route, "createPage", options);
5922
6605
  const routeOptions = this.readRouteOptions(route);
5923
6606
  if (!routeOptions[FLOW_SURFACE_MENU_BINDABLE_OPTION_KEY]) {
5924
6607
  (0, import_errors.throwBadRequest)(`flowSurfaces createPage only accepts a bindable menu route created by createMenu(type="item")`);
@@ -5940,7 +6623,7 @@ class FlowSurfacesService {
5940
6623
  const tabSchemaUid2 = values.tabSchemaUid || (0, import_utils.uid)();
5941
6624
  const tabSchemaName2 = values.tabSchemaName || (0, import_utils.uid)();
5942
6625
  const tabTitle = values.tabTitle || "Untitled";
5943
- await this.db.getRepository("desktopRoutes").create({
6626
+ tabRoute = await this.db.getRepository("desktopRoutes").create({
5944
6627
  values: {
5945
6628
  type: "tabs",
5946
6629
  sort: this.allocateRouteSortValue(1),
@@ -5957,10 +6640,11 @@ class FlowSurfacesService {
5957
6640
  },
5958
6641
  transaction
5959
6642
  });
5960
- tabRoute = await this.db.getRepository("desktopRoutes").findOne({
5961
- filter: { schemaUid: tabSchemaUid2 },
6643
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6644
+ this.readRouteField(tabRoute, "id"),
6645
+ routeScope,
5962
6646
  transaction
5963
- });
6647
+ );
5964
6648
  } else {
5965
6649
  await this.db.getRepository("desktopRoutes").update({
5966
6650
  filterByTk: String(this.readRouteField(tabRoute, "id")),
@@ -5984,6 +6668,13 @@ class FlowSurfacesService {
5984
6668
  transaction
5985
6669
  });
5986
6670
  }
6671
+ if (tabRoute) {
6672
+ await this.attachDesktopRouteScopeToRouteAndChildren(
6673
+ this.readRouteField(tabRoute, "id"),
6674
+ routeScope,
6675
+ transaction
6676
+ );
6677
+ }
5987
6678
  await this.ensureFlowRoutePageSchemaShell(pageSchemaUid, transaction);
5988
6679
  await this.db.getRepository("desktopRoutes").update({
5989
6680
  filterByTk: String(routeId),
@@ -6034,11 +6725,11 @@ class FlowSurfacesService {
6034
6725
  let result;
6035
6726
  if (!import_lodash.default.isNil(values.menuRouteId) && values.menuRouteId !== "") {
6036
6727
  const route = await this.assertMenuRouteBindable(values.menuRouteId, options.transaction);
6037
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6728
+ result = await this.initializeFlowPageForRoute(route, values, options);
6038
6729
  } else {
6039
- const createdMenu = await this.createFlowMenuItem(values, options.transaction);
6730
+ const createdMenu = await this.createFlowMenuItem(values, options);
6040
6731
  const route = await this.assertMenuRouteBindable(createdMenu.routeId, options.transaction);
6041
- result = await this.initializeFlowPageForRoute(route, values, options.transaction);
6732
+ result = await this.initializeFlowPageForRoute(route, values, options);
6042
6733
  }
6043
6734
  await this.persistCreatedKeysForAction("createPage", values, result, options.transaction);
6044
6735
  return result;
@@ -6123,6 +6814,11 @@ class FlowSurfacesService {
6123
6814
  },
6124
6815
  transaction: options.transaction
6125
6816
  });
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);
6126
6822
  const gridUid = await this.ensureGridChild(tabSchemaUid, "BlockGridModel", options.transaction);
6127
6823
  const result = {
6128
6824
  pageUid: pageTarget.uid,