@bleedingdev/modern-js-create 3.2.0-ultramodern.59 → 3.2.0-ultramodern.60

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.
package/dist/index.js CHANGED
@@ -470,11 +470,11 @@ const EN_LOCALE = {
470
470
  optionBffRuntime: ' --bff-runtime Select BFF runtime (hono or effect)',
471
471
  optionTailwind: ' --no-tailwind Disable default Tailwind CSS v4 scaffold',
472
472
  optionWorkspace: ' --workspace Use workspace protocol for @modern-js dependencies (for local monorepo testing)',
473
- optionUltramodernWorkspace: ' --ultramodern-workspace Generate the canonical UltraModern SuperApp workspace',
474
- optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install)',
473
+ optionUltramodernWorkspace: ' --ultramodern-workspace Generate an UltraModern SuperApp workspace (explicit opt-in; default is a simple app)',
474
+ optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install; BleedingDev defaults to install aliases)',
475
475
  optionUltramodernPackageScope: ' --ultramodern-package-scope Publish scope for npm alias installs (for example bleedingdev)',
476
476
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix Prefix for npm alias package names (default: modern-js-)',
477
- optionVertical: ' --vertical Add a full-stack vertical to an existing UltraModern workspace',
477
+ optionVertical: ' --vertical Mutate the current existing UltraModern workspace and wire a MicroVertical named <project-name>',
478
478
  optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
479
479
  examples: '💡 Examples:',
480
480
  example1: ' create my-app',
@@ -486,8 +486,9 @@ const EN_LOCALE = {
486
486
  example7: ' create my-app --bff',
487
487
  example8: ' create my-app --router tanstack --bff-runtime effect',
488
488
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
489
- example10: ' pnpm dlx @bleedingdev/modern-js-create my-super-app',
490
- example11: ' create catalog --vertical',
489
+ example10: ' pnpm dlx @bleedingdev/modern-js-create my-app',
490
+ example11: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
491
+ example12: ' create catalog --vertical',
491
492
  moreInfo: '📚 Learn more: https://modernjs.dev'
492
493
  },
493
494
  version: {
@@ -527,11 +528,11 @@ const ZH_LOCALE = {
527
528
  optionBffRuntime: ' --bff-runtime 选择 BFF 运行时(hono 或 effect)',
528
529
  optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 模板',
529
530
  optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
530
- optionUltramodernWorkspace: ' --ultramodern-workspace 生成标准 UltraModern SuperApp 工作区',
531
- optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install)',
531
+ optionUltramodernWorkspace: ' --ultramodern-workspace 生成 UltraModern SuperApp 工作区(显式选择;默认创建简单应用)',
532
+ optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install;BleedingDev 默认使用 install alias)',
532
533
  optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
533
534
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
534
- optionVertical: ' --vertical 向现有 UltraModern 工作区添加全栈 Vertical',
535
+ optionVertical: ' --vertical 修改当前已有的 UltraModern 工作区,并接入名为 <项目名称> 的 MicroVertical',
535
536
  optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
536
537
  examples: '💡 示例:',
537
538
  example1: ' create my-app',
@@ -543,8 +544,9 @@ const ZH_LOCALE = {
543
544
  example7: ' create my-app --bff',
544
545
  example8: ' create my-app --router tanstack --bff-runtime effect',
545
546
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
546
- example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
547
- example11: ' create catalog --vertical',
547
+ example10: ' pnpm dlx @bleedingdev/modern-js-create my-app',
548
+ example11: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
549
+ example12: ' create catalog --vertical',
548
550
  moreInfo: '📚 更多信息: https://modernjs.dev'
549
551
  },
550
552
  version: {
@@ -621,11 +623,7 @@ const shellApp = {
621
623
  portEnv: 'SHELL_SUPER_APP_PORT',
622
624
  port: 3020,
623
625
  mfName: 'shellSuperApp',
624
- verticalRefs: [
625
- 'explore',
626
- 'decide',
627
- 'checkout'
628
- ],
626
+ verticalRefs: [],
629
627
  ownership: {
630
628
  team: 'super-app-platform',
631
629
  slack: '#super-app-platform',
@@ -641,130 +639,12 @@ const shellApp = {
641
639
  }
642
640
  }
643
641
  };
644
- const verticalApps = [
645
- {
646
- id: 'explore',
647
- directory: 'verticals/explore',
648
- packageSuffix: 'explore',
649
- displayName: 'Explore Vertical',
650
- kind: 'vertical',
651
- domain: 'explore',
652
- portEnv: 'VERTICAL_EXPLORE_PORT',
653
- port: 3021,
654
- mfName: 'verticalExplore',
655
- exposes: {
656
- './Footer': './src/components/footer.tsx',
657
- './Header': './src/components/header.tsx',
658
- './Recommendations': './src/components/recommendations.tsx',
659
- './Route': './src/federation-entry.tsx',
660
- './StorePicker': './src/components/store-picker.tsx'
661
- },
662
- effectApi: {
663
- stem: 'explore',
664
- prefix: '/explore-api',
665
- consumedBy: [
666
- shellApp.id,
667
- 'explore'
668
- ]
669
- },
670
- ownership: {
671
- team: 'tractor-explore',
672
- slack: '#tractor-explore',
673
- pagerDuty: 'pd-tractor-explore',
674
- runbookRef: 'runbooks/wave2/explore.md',
675
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#explore',
676
- blastRadius: {
677
- tier: 'tier-1-tractor-discovery',
678
- references: [
679
- 'docs/super-app-rfc-adr/wave2/blast-radius.md#explore',
680
- 'docs/super-app-rfc-adr/wave2/rollback.md#explore-lkg'
681
- ]
682
- }
683
- }
684
- },
685
- {
686
- id: 'decide',
687
- directory: 'verticals/decide',
688
- packageSuffix: 'decide',
689
- displayName: 'Decide Vertical',
690
- kind: 'vertical',
691
- domain: 'decide',
692
- portEnv: 'VERTICAL_DECIDE_PORT',
693
- port: 3022,
694
- mfName: 'verticalDecide',
695
- verticalRefs: [
696
- 'explore',
697
- 'checkout'
698
- ],
699
- exposes: {
700
- './ProductPage': './src/components/product-page.tsx',
701
- './Route': './src/federation-entry.tsx'
702
- },
703
- effectApi: {
704
- stem: 'decide',
705
- prefix: '/decide-api',
706
- consumedBy: [
707
- shellApp.id,
708
- 'decide'
709
- ]
710
- },
711
- ownership: {
712
- team: 'tractor-decide',
713
- slack: '#tractor-decide',
714
- pagerDuty: 'pd-tractor-decide',
715
- runbookRef: 'runbooks/wave2/decide.md',
716
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#decide',
717
- blastRadius: {
718
- tier: 'tier-1-tractor-configuration',
719
- references: [
720
- 'docs/super-app-rfc-adr/wave2/blast-radius.md#decide',
721
- 'docs/super-app-rfc-adr/wave2/rollback.md#decide-lkg'
722
- ]
723
- }
724
- }
725
- },
726
- {
727
- id: 'checkout',
728
- directory: 'verticals/checkout',
729
- packageSuffix: 'checkout',
730
- displayName: 'Checkout Vertical',
731
- kind: 'vertical',
732
- domain: 'checkout',
733
- portEnv: 'VERTICAL_CHECKOUT_PORT',
734
- port: 3023,
735
- mfName: 'verticalCheckout',
736
- exposes: {
737
- './AddToCart': './src/components/add-to-cart.tsx',
738
- './CartPage': './src/components/cart-page.tsx',
739
- './CheckoutPage': './src/components/checkout-page.tsx',
740
- './MiniCart': './src/components/mini-cart.tsx',
741
- './Route': './src/federation-entry.tsx',
742
- './ThanksPage': './src/components/thanks-page.tsx'
743
- },
744
- effectApi: {
745
- stem: 'checkout',
746
- prefix: '/checkout-api',
747
- consumedBy: [
748
- shellApp.id,
749
- 'checkout'
750
- ]
751
- },
752
- ownership: {
753
- team: 'tractor-checkout',
754
- slack: '#tractor-checkout',
755
- pagerDuty: 'pd-tractor-checkout',
756
- runbookRef: 'runbooks/wave2/checkout.md',
757
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#checkout',
758
- blastRadius: {
759
- tier: 'tier-1-tractor-purchase',
760
- references: [
761
- 'docs/super-app-rfc-adr/wave2/blast-radius.md#checkout',
762
- 'docs/super-app-rfc-adr/wave2/rollback.md#checkout-lkg'
763
- ]
764
- }
765
- }
766
- }
767
- ];
642
+ function createShellHost(remotes = []) {
643
+ return {
644
+ ...shellApp,
645
+ verticalRefs: remotes.map((remote)=>remote.id)
646
+ };
647
+ }
768
648
  const effectDiagnostics = [
769
649
  'anyUnknownInErrorContext',
770
650
  'classSelfMismatch',
@@ -901,20 +781,16 @@ function createVerticalDescriptor(name, port) {
901
781
  ownership: createNeutralOwnership(id)
902
782
  };
903
783
  }
904
- function serviceApiPrefix(service) {
905
- const name = service.id.replace(/^service-/, '').replace(/-effect$/, '');
906
- return name.endsWith('-api') ? `/${name}` : `/${name}-api`;
907
- }
908
784
  function appHasEffectApi(app) {
909
785
  return void 0 !== app.effectApi;
910
786
  }
911
787
  function effectApiPrefix(target) {
912
- return target.effectApi?.prefix ?? serviceApiPrefix(target);
788
+ return target.effectApi?.prefix ?? `/${toKebabCase(target.id)}-api`;
913
789
  }
914
790
  function effectApiStem(target) {
915
- return target.effectApi?.stem ?? target.id.replace(/^service-/, '').replace(/-effect$/, '').replace(/-api$/, '');
791
+ return target.effectApi?.stem ?? toKebabCase(target.id).replace(/-api$/, '');
916
792
  }
917
- function verticalEffectApps(remotes = verticalApps) {
793
+ function verticalEffectApps(remotes = []) {
918
794
  return remotes.filter(appHasEffectApi);
919
795
  }
920
796
  function normalizePath(filePath) {
@@ -1032,7 +908,7 @@ function modernPackageSpecifier(packageName, packageSource) {
1032
908
  if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
1033
909
  return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
1034
910
  }
1035
- function appDependencies(scope, packageSource, app) {
911
+ function appDependencies(scope, packageSource, app, remotes = []) {
1036
912
  const dependencies = {
1037
913
  '@modern-js/plugin-tanstack': modernPackageSpecifier('@modern-js/plugin-tanstack', packageSource),
1038
914
  '@modern-js/plugin-i18n': modernPackageSpecifier('@modern-js/plugin-i18n', packageSource),
@@ -1050,9 +926,9 @@ function appDependencies(scope, packageSource, app) {
1050
926
  };
1051
927
  if ('shell' === app.kind) {
1052
928
  dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
1053
- for (const remote of verticalEffectApps())dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
929
+ for (const remote of verticalEffectApps(remotes))dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
1054
930
  }
1055
- for (const remote of resolveRemoteRefs(app))dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
931
+ for (const remote of resolveRemoteRefs(app, remotes))dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
1056
932
  if (appHasEffectApi(app)) dependencies['@modern-js/plugin-bff'] = modernPackageSpecifier('@modern-js/plugin-bff', packageSource);
1057
933
  return dependencies;
1058
934
  }
@@ -1074,7 +950,12 @@ function appDevDependencies(packageSource, enableTailwind) {
1074
950
  wrangler: WRANGLER_VERSION
1075
951
  };
1076
952
  }
1077
- function createRootPackageJson(scope, packageSource) {
953
+ function createRootPackageJson(scope, packageSource, remotes = []) {
954
+ const shellFilter = `--filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)}`;
955
+ const remoteFilters = remotes.map((remote)=>`--filter ${ultramodern_workspace_packageName(scope, remote.packageSuffix)}`);
956
+ const remoteBuildPrefix = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run build && ' : '';
957
+ const remoteCloudflareBuildPrefix = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:build && ' : '';
958
+ const remoteCloudflareDeployPrefix = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && ' : '';
1078
959
  return {
1079
960
  private: true,
1080
961
  name: scope,
@@ -1082,19 +963,23 @@ function createRootPackageJson(scope, packageSource) {
1082
963
  type: 'module',
1083
964
  packageManager: `pnpm@${PNPM_VERSION}`,
1084
965
  scripts: {
1085
- dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'explore')} --filter ${ultramodern_workspace_packageName(scope, 'decide')} --filter ${ultramodern_workspace_packageName(scope, 'checkout')} dev`,
966
+ dev: `pnpm --parallel ${[
967
+ shellFilter,
968
+ ...remoteFilters
969
+ ].join(' ')} dev`,
1086
970
  'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
1087
- 'dev:explore': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'explore')} dev`,
1088
- 'dev:decide': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'decide')} dev`,
1089
- 'dev:checkout': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'checkout')} dev`,
1090
- build: 'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
971
+ ...Object.fromEntries(remotes.map((remote)=>[
972
+ `dev:${remote.packageSuffix}`,
973
+ `pnpm --filter ${ultramodern_workspace_packageName(scope, remote.packageSuffix)} dev`
974
+ ])),
975
+ build: `${remoteBuildPrefix}pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types`,
1091
976
  format: 'oxfmt .',
1092
977
  'format:check': 'oxfmt --check .',
1093
978
  lint: 'oxlint .',
1094
979
  'lint:fix': 'oxlint . --fix',
1095
980
  typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
1096
- 'cloudflare:build': 'pnpm -r --filter "./verticals/*" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
1097
- 'cloudflare:deploy': 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy',
981
+ 'cloudflare:build': `${remoteCloudflareBuildPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types`,
982
+ 'cloudflare:deploy': `${remoteCloudflareDeployPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:deploy`,
1098
983
  'cloudflare:proof': "node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json",
1099
984
  'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
1100
985
  'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
@@ -1142,11 +1027,11 @@ function remoteDependencyAlias(remote) {
1142
1027
  function zephyrRemoteDependency(scope, remote) {
1143
1028
  return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
1144
1029
  }
1145
- function resolveRemoteRefs(app, remotes = verticalApps) {
1030
+ function resolveRemoteRefs(app, remotes = []) {
1146
1031
  const verticalRefs = app.verticalRefs ?? [];
1147
1032
  return verticalRefs.map((remoteRef)=>remotes.find((remote)=>remote.id === remoteRef)).filter((remote)=>void 0 !== remote);
1148
1033
  }
1149
- function createModuleFederationRemoteContracts(app, remotes = verticalApps) {
1034
+ function createModuleFederationRemoteContracts(app, remotes = []) {
1150
1035
  return resolveRemoteRefs(app, remotes).map((remote)=>({
1151
1036
  id: remote.id,
1152
1037
  alias: remoteDependencyAlias(remote),
@@ -1155,7 +1040,7 @@ function createModuleFederationRemoteContracts(app, remotes = verticalApps) {
1155
1040
  manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
1156
1041
  }));
1157
1042
  }
1158
- function createZephyrDependencies(scope, app, remotes = verticalApps) {
1043
+ function createZephyrDependencies(scope, app, remotes = []) {
1159
1044
  if (!app.verticalRefs?.length) return {};
1160
1045
  return Object.fromEntries(resolveRemoteRefs(app, remotes).map((remote)=>[
1161
1046
  remoteDependencyAlias(remote),
@@ -1262,7 +1147,7 @@ function createPackageTsConfig(packageDir, includeApi = false) {
1262
1147
  ]
1263
1148
  };
1264
1149
  }
1265
- function createAppPackage(scope, app, packageSource, enableTailwind) {
1150
+ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = []) {
1266
1151
  const packageExports = Object.fromEntries(Object.entries(app.exposes ?? {}).map(([expose, source])=>[
1267
1152
  expose,
1268
1153
  source
@@ -1275,7 +1160,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
1275
1160
  dev: 'modern dev',
1276
1161
  build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
1277
1162
  'cloudflare:build': 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
1278
- 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
1163
+ 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
1279
1164
  'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
1280
1165
  'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mjs --app ${app.id}`,
1281
1166
  serve: 'modern serve',
@@ -1290,8 +1175,8 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
1290
1175
  apiRuntime: 'effect-bff'
1291
1176
  } : {}
1292
1177
  },
1293
- 'zephyr:dependencies': createZephyrDependencies(scope, app),
1294
- dependencies: appDependencies(scope, packageSource, app),
1178
+ 'zephyr:dependencies': createZephyrDependencies(scope, app, remotes),
1179
+ dependencies: appDependencies(scope, packageSource, app, remotes),
1295
1180
  devDependencies: appDevDependencies(packageSource, enableTailwind)
1296
1181
  };
1297
1182
  if (appHasEffectApi(app)) Object.assign(packageExports, {
@@ -1299,7 +1184,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
1299
1184
  './shared/effect/api': './shared/effect/api.ts'
1300
1185
  });
1301
1186
  else if ('shell' === app.kind) Object.assign(packageExports, {
1302
- './effect/clients': './src/effect/recommendations-client.ts'
1187
+ './effect/clients': './src/effect/vertical-clients.ts'
1303
1188
  });
1304
1189
  if (Object.keys(packageExports).length > 0) packageJson.exports = packageExports;
1305
1190
  return packageJson;
@@ -1563,7 +1448,7 @@ ${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
1563
1448
  function createRemoteManifestEnv(remote) {
1564
1449
  return `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
1565
1450
  }
1566
- function createModuleFederationRemoteUrlHelpers(app, remotes = verticalApps) {
1451
+ function createModuleFederationRemoteUrlHelpers(app, remotes = []) {
1567
1452
  if (0 === resolveRemoteRefs(app, remotes).length) return '';
1568
1453
  return `const cloudflareDeployEnabled =
1569
1454
  process.env['MODERNJS_DEPLOY'] === 'cloudflare';
@@ -1604,7 +1489,7 @@ function createRemoteManifestUrl(options: {
1604
1489
 
1605
1490
  `;
1606
1491
  }
1607
- function createModuleFederationRemotesConfig(scope, app, remotes = verticalApps) {
1492
+ function createModuleFederationRemotesConfig(scope, app, remotes = []) {
1608
1493
  const remoteEntries = resolveRemoteRefs(app, remotes).map((remote)=>{
1609
1494
  const key = remoteDependencyAlias(remote);
1610
1495
  return ` ${key}: createRemoteManifestUrl({
@@ -1621,7 +1506,7 @@ ${remoteEntries}
1621
1506
  },
1622
1507
  `;
1623
1508
  }
1624
- function createShellModuleFederationConfig(scope, remotes = verticalApps) {
1509
+ function createShellModuleFederationConfig(scope, remotes = []) {
1625
1510
  const shellHost = {
1626
1511
  ...shellApp,
1627
1512
  verticalRefs: remotes.map((remote)=>remote.id)
@@ -1679,7 +1564,7 @@ export const ultramodernApiMarker = {
1679
1564
  } as const;
1680
1565
  `;
1681
1566
  }
1682
- function createRemoteModuleFederationConfig(scope, app, remotes = verticalApps) {
1567
+ function createRemoteModuleFederationConfig(scope, app, remotes = []) {
1683
1568
  const exposes = formatTsObjectLiteral(app.exposes ?? {});
1684
1569
  return `// @effect-diagnostics nodeBuiltinImport:off
1685
1570
  import { createRequire } from 'node:module';
@@ -1732,192 +1617,152 @@ function createRouteOwnedI18nPaths(app) {
1732
1617
  en: '/'
1733
1618
  },
1734
1619
  titleKey: 'shell.title'
1735
- },
1736
- {
1737
- ...base,
1738
- canonicalPath: '/tractors',
1739
- id: 'shell-tractors',
1740
- localisedPaths: {
1741
- cs: '/traktory',
1742
- en: '/tractors'
1743
- },
1744
- titleKey: 'shell.routes.listing'
1745
- },
1746
- {
1747
- ...base,
1748
- canonicalPath: '/stores',
1749
- id: 'shell-stores',
1750
- localisedPaths: {
1751
- cs: '/prodejci',
1752
- en: '/stores'
1753
- },
1754
- titleKey: 'shell.routes.storePicker'
1755
- },
1756
- {
1757
- ...base,
1758
- canonicalPath: '/tractors/:slug',
1759
- id: 'shell-product-detail',
1760
- localisedPaths: {
1761
- cs: '/traktory/:slug',
1762
- en: '/tractors/:slug'
1763
- },
1764
- titleKey: 'shell.routes.productDetail'
1765
- },
1766
- {
1767
- ...base,
1768
- canonicalPath: '/cart',
1769
- id: 'shell-cart',
1770
- localisedPaths: {
1771
- cs: '/kosik',
1772
- en: '/cart'
1773
- },
1774
- titleKey: 'shell.routes.cart'
1775
1620
  }
1776
1621
  ];
1777
- if ('explore' === app.domain) return [
1622
+ if ('workspace' === app.domain) return [
1778
1623
  {
1779
1624
  ...base,
1780
1625
  canonicalPath: '/',
1781
- id: 'explore-home',
1626
+ id: 'workspace-home',
1782
1627
  localisedPaths: {
1783
1628
  cs: '/',
1784
1629
  en: '/'
1785
1630
  },
1786
- titleKey: 'explore.title'
1631
+ titleKey: 'workspace.title'
1787
1632
  },
1788
1633
  {
1789
1634
  ...base,
1790
- canonicalPath: '/tractors',
1791
- id: 'explore-listing',
1635
+ canonicalPath: '/workspaces',
1636
+ id: 'workspace-listing',
1792
1637
  localisedPaths: {
1793
- cs: '/traktory',
1794
- en: '/tractors'
1638
+ cs: '/pracovni-prostory',
1639
+ en: '/workspaces'
1795
1640
  },
1796
- titleKey: 'explore.routes.listing'
1641
+ titleKey: 'workspace.routes.workspaces'
1797
1642
  },
1798
1643
  {
1799
1644
  ...base,
1800
- canonicalPath: '/stores',
1801
- id: 'explore-store-picker',
1645
+ canonicalPath: '/directory',
1646
+ id: 'workspace-directory',
1802
1647
  localisedPaths: {
1803
- cs: '/prodejci',
1804
- en: '/stores'
1648
+ cs: '/adresar',
1649
+ en: '/directory'
1805
1650
  },
1806
- titleKey: 'explore.routes.storePicker'
1651
+ titleKey: 'workspace.routes.directory'
1807
1652
  },
1808
1653
  {
1809
1654
  ...base,
1810
1655
  canonicalPath: '/unavailable',
1811
- id: 'explore-unavailable',
1656
+ id: 'workspace-unavailable',
1812
1657
  localisedPaths: {
1813
1658
  cs: '/nedostupne',
1814
1659
  en: '/unavailable'
1815
1660
  },
1816
- titleKey: 'explore.routes.unavailable'
1661
+ titleKey: 'workspace.routes.unavailable'
1817
1662
  }
1818
1663
  ];
1819
- if ('decide' === app.domain) return [
1664
+ if ('records' === app.domain) return [
1820
1665
  {
1821
1666
  ...base,
1822
1667
  canonicalPath: '/',
1823
- id: 'decide-home',
1668
+ id: 'records-home',
1824
1669
  localisedPaths: {
1825
1670
  cs: '/',
1826
1671
  en: '/'
1827
1672
  },
1828
- titleKey: 'decide.title'
1673
+ titleKey: 'records.title'
1829
1674
  },
1830
1675
  {
1831
1676
  ...base,
1832
- canonicalPath: '/tractors',
1833
- id: 'decide-listing-parent',
1677
+ canonicalPath: '/workspaces',
1678
+ id: 'records-workspace-parent',
1834
1679
  localisedPaths: {
1835
- cs: '/traktory',
1836
- en: '/tractors'
1680
+ cs: '/pracovni-prostory',
1681
+ en: '/workspaces'
1837
1682
  },
1838
- titleKey: 'decide.routes.listing'
1683
+ titleKey: 'records.routes.workspaces'
1839
1684
  },
1840
1685
  {
1841
1686
  ...base,
1842
- canonicalPath: '/tractors/:slug',
1843
- id: 'decide-product-detail',
1687
+ canonicalPath: '/records/:slug',
1688
+ id: 'records-detail',
1844
1689
  localisedPaths: {
1845
- cs: '/traktory/:slug',
1846
- en: '/tractors/:slug'
1690
+ cs: '/zaznamy/:slug',
1691
+ en: '/records/:slug'
1847
1692
  },
1848
- titleKey: 'decide.routes.productDetail'
1693
+ titleKey: 'records.routes.recordDetail'
1849
1694
  },
1850
1695
  {
1851
1696
  ...base,
1852
1697
  canonicalPath: '/unavailable',
1853
- id: 'decide-unavailable',
1698
+ id: 'records-unavailable',
1854
1699
  localisedPaths: {
1855
1700
  cs: '/nedostupne',
1856
1701
  en: '/unavailable'
1857
1702
  },
1858
- titleKey: 'decide.routes.unavailable'
1703
+ titleKey: 'records.routes.unavailable'
1859
1704
  }
1860
1705
  ];
1861
- if ('checkout' === app.domain) return [
1706
+ if ('actions' === app.domain) return [
1862
1707
  {
1863
1708
  ...base,
1864
1709
  canonicalPath: '/',
1865
- id: 'checkout-home',
1710
+ id: 'actions-home',
1866
1711
  localisedPaths: {
1867
1712
  cs: '/',
1868
1713
  en: '/'
1869
1714
  },
1870
- titleKey: 'checkout.title'
1715
+ titleKey: 'actions.title'
1871
1716
  },
1872
1717
  {
1873
1718
  ...base,
1874
- canonicalPath: '/cart',
1875
- id: 'checkout-cart',
1719
+ canonicalPath: '/actions',
1720
+ id: 'actions-queue',
1876
1721
  localisedPaths: {
1877
- cs: '/kosik',
1878
- en: '/cart'
1722
+ cs: '/akce',
1723
+ en: '/actions'
1879
1724
  },
1880
- titleKey: 'checkout.routes.cart'
1725
+ titleKey: 'actions.routes.actions'
1881
1726
  },
1882
1727
  {
1883
1728
  ...base,
1884
- canonicalPath: '/checkout',
1885
- id: 'checkout-start',
1729
+ canonicalPath: '/actions/review',
1730
+ id: 'actions-review',
1886
1731
  localisedPaths: {
1887
- cs: '/pokladna',
1888
- en: '/checkout'
1732
+ cs: '/akce/revize',
1733
+ en: '/actions/review'
1889
1734
  },
1890
- titleKey: 'checkout.routes.checkout'
1735
+ titleKey: 'actions.routes.review'
1891
1736
  },
1892
1737
  {
1893
1738
  ...base,
1894
- canonicalPath: '/checkout/thank-you',
1895
- id: 'checkout-thank-you-parent',
1739
+ canonicalPath: '/actions/done',
1740
+ id: 'actions-done-parent',
1896
1741
  localisedPaths: {
1897
- cs: '/pokladna/dekujeme',
1898
- en: '/checkout/thank-you'
1742
+ cs: '/akce/hotovo',
1743
+ en: '/actions/done'
1899
1744
  },
1900
- titleKey: 'checkout.routes.thankYou'
1745
+ titleKey: 'actions.routes.done'
1901
1746
  },
1902
1747
  {
1903
1748
  ...base,
1904
- canonicalPath: '/checkout/thank-you/:orderId?',
1905
- id: 'checkout-thank-you',
1749
+ canonicalPath: '/actions/done/:actionId?',
1750
+ id: 'actions-done',
1906
1751
  localisedPaths: {
1907
- cs: '/pokladna/dekujeme/:orderId?',
1908
- en: '/checkout/thank-you/:orderId?'
1752
+ cs: '/akce/hotovo/:actionId?',
1753
+ en: '/actions/done/:actionId?'
1909
1754
  },
1910
- titleKey: 'checkout.routes.thankYou'
1755
+ titleKey: 'actions.routes.done'
1911
1756
  },
1912
1757
  {
1913
1758
  ...base,
1914
1759
  canonicalPath: '/unavailable',
1915
- id: 'checkout-unavailable',
1760
+ id: 'actions-unavailable',
1916
1761
  localisedPaths: {
1917
1762
  cs: '/nedostupne',
1918
1763
  en: '/unavailable'
1919
1764
  },
1920
- titleKey: 'checkout.routes.unavailable'
1765
+ titleKey: 'actions.routes.unavailable'
1921
1766
  }
1922
1767
  ];
1923
1768
  return [
@@ -1983,7 +1828,24 @@ function createRouteAliasPage(canonicalPath) {
1983
1828
  return `export { default } from '${rootPageImport}';
1984
1829
  `;
1985
1830
  }
1986
- function createAppEnvDts(app, remotes = verticalApps) {
1831
+ function createBoundaryDebugMetadata(scope, remotes = []) {
1832
+ return {
1833
+ schemaVersion: 1,
1834
+ appId: shellApp.id,
1835
+ boundaries: [
1836
+ shellApp,
1837
+ ...remotes
1838
+ ].map((app)=>({
1839
+ appId: app.id,
1840
+ mfName: app.mfName,
1841
+ packageName: ultramodern_workspace_packageName(scope, app.packageSuffix),
1842
+ role: 'shell' === app.kind ? 'host' : 'vertical',
1843
+ ownerTeam: app.ownership.team,
1844
+ label: app.displayName
1845
+ }))
1846
+ };
1847
+ }
1848
+ function createAppEnvDts(app, remotes = []) {
1987
1849
  const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
1988
1850
  const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
1989
1851
  return `declare module '${moduleName}' {
@@ -2001,24 +1863,15 @@ declare module '*.svg' {
2001
1863
  }
2002
1864
  ${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
2003
1865
  }
2004
- function createAppRuntimeConfig(app) {
2005
- const namespace = appI18nNamespace(app);
2006
- const localeMessages = (language)=>{
2007
- if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
2008
- return Object.assign({}, createAppLocaleMessages(app, language), ...verticalApps.map((remote)=>createAppLocaleMessages(remote, language)));
2009
- };
2010
- const resources = {
2011
- cs: {
2012
- [namespace]: localeMessages('cs'),
2013
- translation: localeMessages('cs')
2014
- },
2015
- en: {
2016
- [namespace]: localeMessages('en'),
2017
- translation: localeMessages('en')
2018
- }
2019
- };
1866
+ function createAppRuntimeConfig(app, scope, remotes = []) {
1867
+ const pluginsConfig = 'shell' === app.kind ? ` plugins: [
1868
+ ultramodernBoundaryDebuggerPlugin({
1869
+ metadata: ${JSON.stringify(createBoundaryDebugMetadata(scope, remotes), null, 6).split('\n').join('\n ')},
1870
+ }),
1871
+ ],
1872
+ ` : '';
2020
1873
  return `import { defineRuntimeConfig } from '@modern-js/runtime';
2021
- import { createInstance } from 'i18next';
1874
+ ${'shell' === app.kind ? "import { ultramodernBoundaryDebuggerPlugin } from '@modern-js/runtime/boundary-debugger';\n" : ''}import { createInstance } from 'i18next';
2022
1875
  import { ultramodernRouteNamespace } from './routes/ultramodern-route-metadata';
2023
1876
 
2024
1877
  const i18nInstance = createInstance();
@@ -2033,13 +1886,13 @@ export default defineRuntimeConfig({
2033
1886
  escapeValue: false,
2034
1887
  },
2035
1888
  ns: [ultramodernRouteNamespace, 'translation'],
2036
- resources: ${JSON.stringify(resources, null, 8).split('\n').join('\n ')},
2037
1889
  supportedLngs: ['en', 'cs'],
2038
1890
  },
2039
1891
  },
2040
1892
  router: {
2041
1893
  framework: 'tanstack',
2042
1894
  },
1895
+ ${pluginsConfig}
2043
1896
  });
2044
1897
  `;
2045
1898
  }
@@ -2055,21 +1908,12 @@ function tailwindPrefixForApp(app) {
2055
1908
  if ('shell' === app.kind) return 'shell';
2056
1909
  return createTailwindPrefix(app.domain ?? app.id);
2057
1910
  }
2058
- function tailwindPrefixForService(service) {
2059
- return createTailwindPrefix(service.id);
2060
- }
2061
- function assertUniqueTailwindPrefixes(apps, services = []) {
1911
+ function assertUniqueTailwindPrefixes(apps) {
2062
1912
  const seen = new Map();
2063
- const entries = [
2064
- ...apps.map((app)=>[
2065
- app.id,
2066
- tailwindPrefixForApp(app)
2067
- ]),
2068
- ...services.map((service)=>[
2069
- service.id,
2070
- tailwindPrefixForService(service)
2071
- ])
2072
- ];
1913
+ const entries = apps.map((app)=>[
1914
+ app.id,
1915
+ tailwindPrefixForApp(app)
1916
+ ]);
2073
1917
  for (const [id, prefix] of entries){
2074
1918
  const previous = seen.get(prefix);
2075
1919
  if (previous) throw new Error(`Tailwind prefix ${prefix} for ${id} collides with ${previous}`);
@@ -2106,115 +1950,7 @@ export default {
2106
1950
  function createTw(prefix) {
2107
1951
  return (classList)=>classList.split(/\s+/u).filter(Boolean).map((candidate)=>`${prefix}:${candidate.replace(/\[&&\]:/gu, '')}`).join(' ');
2108
1952
  }
2109
- function createCommerceAssetSvg(title, palette) {
2110
- return `<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900" viewBox="0 0 1440 900" role="img" aria-label="${title}">
2111
- <defs>
2112
- <linearGradient id="sky" x1="0" x2="0" y1="0" y2="1">
2113
- <stop offset="0" stop-color="${palette.sky}"/>
2114
- <stop offset="1" stop-color="#fff8dc"/>
2115
- </linearGradient>
2116
- <linearGradient id="field" x1="0" x2="1" y1="0" y2="1">
2117
- <stop offset="0" stop-color="${palette.ground}"/>
2118
- <stop offset="1" stop-color="${palette.accent}"/>
2119
- </linearGradient>
2120
- </defs>
2121
- <rect width="1440" height="900" fill="url(#sky)"/>
2122
- <path d="M0 566c172-78 330-102 474-72 125 26 219 91 340 106 170 21 339-74 626-43v343H0z" fill="url(#field)"/>
2123
- <path d="M0 686c205-70 451-66 738 12 287 77 521 66 702-33v235H0z" fill="#4f7f38" opacity=".55"/>
2124
- <g fill="none" stroke="#fff6b7" stroke-linecap="round" stroke-width="10" opacity=".55">
2125
- <path d="M108 820c205-138 382-202 530-192"/>
2126
- <path d="M322 862c176-134 338-198 486-193"/>
2127
- <path d="M583 886c119-121 260-190 422-207"/>
2128
- <path d="M868 878c95-94 207-153 336-176"/>
2129
- </g>
2130
- <g transform="translate(430 430)">
2131
- <circle cx="170" cy="210" r="92" fill="#161616"/>
2132
- <circle cx="170" cy="210" r="54" fill="#d7c46d"/>
2133
- <circle cx="514" cy="214" r="108" fill="#161616"/>
2134
- <circle cx="514" cy="214" r="63" fill="#d7c46d"/>
2135
- <path d="M194 142h194l72-100h114c49 0 89 39 89 88v57H625l-51-90H452l-78 114H209z" fill="${palette.tractor}"/>
2136
- <path d="M283 67h134l-54 73H249z" fill="#c9ecff" opacity=".72"/>
2137
- <path d="M120 184h430v54H120z" fill="${palette.tractor}"/>
2138
- <path d="M578 52l60-35M618 37l34 72" stroke="#171717" stroke-linecap="round" stroke-width="14"/>
2139
- <path d="M90 236h578" stroke="#171717" stroke-linecap="round" stroke-width="18"/>
2140
- </g>
2141
- </svg>
2142
- `;
2143
- }
2144
- const commerceAssetPublicRoot = 'assets/ultramodern';
2145
- function commerceAssetPublicPath(filename) {
2146
- return `${commerceAssetPublicRoot}/${filename}`;
2147
- }
2148
- function commerceAssetUrl(filename) {
2149
- return `/${commerceAssetPublicRoot}/${filename}`;
2150
- }
2151
- function commerceAssetsForApp(app) {
2152
- if ('shell' === app.kind) return {
2153
- [commerceAssetPublicPath('hero-field.svg')]: createCommerceAssetSvg('Tractor crossing cultivated fields', {
2154
- accent: '#d6b85d',
2155
- ground: '#84ad58',
2156
- sky: '#9fd6e8',
2157
- tractor: '#005f73'
2158
- }),
2159
- [commerceAssetPublicPath('autonomy.svg')]: createCommerceAssetSvg('Autonomous tractor concept', {
2160
- accent: '#c26a2e',
2161
- ground: '#668f55',
2162
- sky: '#d5e7de',
2163
- tractor: '#f2a51a'
2164
- }),
2165
- [commerceAssetPublicPath('field-loader.svg')]: createCommerceAssetSvg('Field Loader 112 tractor', {
2166
- accent: '#d6b85d',
2167
- ground: '#84ad58',
2168
- sky: '#9fd6e8',
2169
- tractor: '#00624b'
2170
- }),
2171
- [commerceAssetPublicPath('orchard.svg')]: createCommerceAssetSvg('Orchard tractor between tight rows', {
2172
- accent: '#b45b2d',
2173
- ground: '#6f9b4a',
2174
- sky: '#c9ebff',
2175
- tractor: '#1d5d9b'
2176
- }),
2177
- [commerceAssetPublicPath('vineyard.svg')]: createCommerceAssetSvg('Vineyard narrow tractor', {
2178
- accent: '#b88d58',
2179
- ground: '#5e8a45',
2180
- sky: '#f1dcb9',
2181
- tractor: '#914d76'
2182
- })
2183
- };
2184
- if ('explore' === app.id) return {
2185
- [commerceAssetPublicPath('autonomy.svg')]: createCommerceAssetSvg('Autonomous tractor concept', {
2186
- accent: '#c26a2e',
2187
- ground: '#668f55',
2188
- sky: '#d5e7de',
2189
- tractor: '#f2a51a'
2190
- }),
2191
- [commerceAssetPublicPath('field-loader.svg')]: createCommerceAssetSvg('Field Loader 112 tractor', {
2192
- accent: '#d6b85d',
2193
- ground: '#84ad58',
2194
- sky: '#9fd6e8',
2195
- tractor: '#00624b'
2196
- }),
2197
- [commerceAssetPublicPath('orchard.svg')]: createCommerceAssetSvg('Orchard tractor between tight rows', {
2198
- accent: '#b45b2d',
2199
- ground: '#6f9b4a',
2200
- sky: '#c9ebff',
2201
- tractor: '#1d5d9b'
2202
- }),
2203
- [commerceAssetPublicPath('vineyard.svg')]: createCommerceAssetSvg('Vineyard narrow tractor', {
2204
- accent: '#b88d58',
2205
- ground: '#5e8a45',
2206
- sky: '#f1dcb9',
2207
- tractor: '#914d76'
2208
- })
2209
- };
2210
- if ('decide' === app.id) return {
2211
- [commerceAssetPublicPath('field-loader.svg')]: createCommerceAssetSvg('Field Loader 112 tractor detail', {
2212
- accent: '#d6b85d',
2213
- ground: '#84ad58',
2214
- sky: '#9fd6e8',
2215
- tractor: '#00624b'
2216
- })
2217
- };
1953
+ function workspaceAssetsForApp(_app) {
2218
1954
  return {};
2219
1955
  }
2220
1956
  function createLocalizedHeadComponent() {
@@ -2367,21 +2103,20 @@ const LocalizedHead = () => {
2367
2103
  };
2368
2104
  `;
2369
2105
  }
2370
- function createShellPage() {
2106
+ function createShellPage(remotes = []) {
2371
2107
  const tw = createTw(tailwindPrefixForApp(shellApp));
2372
- return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2108
+ const remoteCount = String(remotes.length);
2109
+ return `import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2373
2110
  import { Helmet } from '@modern-js/runtime/head';
2374
2111
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2375
2112
  import ShellFrame from '../shell-frame';
2376
- import { StorePicker } from '../vertical-components';
2113
+ import { VerticalShowcase } from '../vertical-components';
2377
2114
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2378
2115
  import { ultramodernUiMarker } from '../../ultramodern-build';
2379
2116
 
2380
- const heroField = '${commerceAssetUrl('hero-field.svg')}';
2381
-
2382
2117
  ${createLocalizedHeadComponent()}
2383
2118
  export default function ShellHome() {
2384
- const { i18nInstance, language } = useModernI18n();
2119
+ const { i18nInstance } = useModernI18n();
2385
2120
  const t = i18nInstance['t'].bind(i18nInstance);
2386
2121
 
2387
2122
  return (
@@ -2393,17 +2128,30 @@ export default function ShellHome() {
2393
2128
  <h1 className="${tw('mt-3 max-w-3xl text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('shell.title')}</h1>
2394
2129
  <p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('shell.hero.lede')}</p>
2395
2130
  <div className="${tw('mt-7 flex flex-wrap gap-3')}">
2396
- <a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" href={\`/\${language}/tractors/field-loader-112\`}>
2397
- {t('shell.hero.primary')}
2398
- </a>
2399
- <a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}" href={\`/\${language}/tractors\`}>
2400
- {t('shell.hero.secondary')}
2401
- </a>
2131
+ <I18nLink className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" to="/">
2132
+ {t('shell.hero.primary')}
2133
+ </I18nLink>
2134
+ <span className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}">
2135
+ {t('shell.hero.secondary')}
2136
+ </span>
2137
+ </div>
2138
+ </div>
2139
+ <div className="${tw('rounded-3xl bg-white/90 p-6 shadow-2xl shadow-stone-900/15')}">
2140
+ <div className="${tw('grid gap-4 sm:grid-cols-2')}">
2141
+ <article className="${tw('rounded-2xl bg-emerald-50 p-5')}">
2142
+ <span className="${tw('text-sm font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('shell.hero.cardOneKicker')}</span>
2143
+ <strong className="${tw('mt-3 block text-3xl font-black text-stone-950')}">${remoteCount}</strong>
2144
+ <p className="${tw('mt-2 text-sm font-semibold text-stone-600')}">{t('shell.hero.cardOne')}</p>
2145
+ </article>
2146
+ <article className="${tw('rounded-2xl bg-amber-50 p-5')}">
2147
+ <span className="${tw('text-sm font-black uppercase tracking-[0.16em] text-amber-800')}">{t('shell.hero.cardTwoKicker')}</span>
2148
+ <strong className="${tw('mt-3 block text-3xl font-black text-stone-950')}">SSR</strong>
2149
+ <p className="${tw('mt-2 text-sm font-semibold text-stone-600')}">{t('shell.hero.cardTwo')}</p>
2150
+ </article>
2402
2151
  </div>
2403
2152
  </div>
2404
- <img alt="" className="${tw('aspect-[16/10] w-full rounded-3xl bg-stone-200 object-cover shadow-2xl shadow-stone-900/20')}" src={heroField} />
2405
2153
  </section>
2406
- <StorePicker />
2154
+ <VerticalShowcase />
2407
2155
  <p className="${tw('sr-only')}" data-testid="ultramodern-preset">presetUltramodern workspace</p>
2408
2156
  <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2409
2157
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
@@ -2413,94 +2161,19 @@ export default function ShellHome() {
2413
2161
  }
2414
2162
  `;
2415
2163
  }
2416
- function createShellTractorsPage() {
2417
- return `import { Helmet } from '@modern-js/runtime/head';
2418
- import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2419
- import ShellFrame from '../../shell-frame';
2420
- import { Recommendations } from '../../vertical-components';
2421
- import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2422
-
2423
- ${createLocalizedHeadComponent()}
2424
- export default function ShellTractorsPage() {
2425
- return (
2426
- <ShellFrame>
2427
- <LocalizedHead />
2428
- <Recommendations />
2429
- </ShellFrame>
2430
- );
2431
- }
2432
- `;
2433
- }
2434
- function createShellStoresPage() {
2435
- return `import { Helmet } from '@modern-js/runtime/head';
2436
- import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2437
- import ShellFrame from '../../shell-frame';
2438
- import { StorePicker } from '../../vertical-components';
2439
- import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2440
-
2441
- ${createLocalizedHeadComponent()}
2442
- export default function ShellStoresPage() {
2443
- return (
2444
- <ShellFrame>
2445
- <LocalizedHead />
2446
- <StorePicker />
2447
- </ShellFrame>
2448
- );
2449
- }
2450
- `;
2451
- }
2452
- function createShellProductPage() {
2453
- return `import { Helmet } from '@modern-js/runtime/head';
2454
- import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2455
- import ShellFrame from '../../../shell-frame';
2456
- import { ProductPage } from '../../../vertical-components';
2457
- import { ultramodernLocalisedUrls } from '../../../ultramodern-route-metadata';
2458
-
2459
- ${createLocalizedHeadComponent()}
2460
- export default function ShellProductPage() {
2461
- return (
2462
- <ShellFrame boundary="decide">
2463
- <LocalizedHead />
2464
- <ProductPage />
2465
- </ShellFrame>
2466
- );
2467
- }
2468
- `;
2469
- }
2470
- function createShellCartPage() {
2471
- return `import { Helmet } from '@modern-js/runtime/head';
2472
- import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2473
- import ShellFrame from '../../shell-frame';
2474
- import { CartPage } from '../../vertical-components';
2475
- import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2476
-
2477
- ${createLocalizedHeadComponent()}
2478
- export default function ShellCartPage() {
2479
- return (
2480
- <ShellFrame boundary="checkout" showCart={false}>
2481
- <LocalizedHead />
2482
- <CartPage />
2483
- </ShellFrame>
2484
- );
2485
- }
2486
- `;
2487
- }
2488
2164
  function createShellFrameComponent() {
2489
2165
  const tw = createTw(tailwindPrefixForApp(shellApp));
2490
2166
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2491
2167
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2492
2168
  import type { ReactNode } from 'react';
2493
- import BoundaryOverlay from './boundary-overlay';
2494
- import { Header, MiniCart } from './vertical-components';
2169
+ import { Header, StatusBadge } from './vertical-components';
2495
2170
  import { ultramodernLocalisedUrls } from './ultramodern-route-metadata';
2496
2171
 
2497
2172
  const supportedLanguages = ['en', 'cs'] as const;
2498
2173
  type SupportedLanguage = (typeof supportedLanguages)[number];
2499
2174
 
2500
2175
  type ShellFrameProps = {
2501
- boundary?: 'checkout' | 'decide' | 'explore';
2502
2176
  children: ReactNode;
2503
- showCart?: boolean;
2504
2177
  };
2505
2178
 
2506
2179
  const localisedUrls = ultramodernLocalisedUrls as Record<
@@ -2618,14 +2291,14 @@ const locationSuffix = (location: {
2618
2291
  return \`\${locationSearch}\${locationHash}\`;
2619
2292
  };
2620
2293
 
2621
- export default function ShellFrame({ boundary = 'explore', children, showCart = true }: ShellFrameProps) {
2294
+ export default function ShellFrame({ children }: ShellFrameProps) {
2622
2295
  const { i18nInstance, language } = useModernI18n();
2623
2296
  const t = i18nInstance['t'].bind(i18nInstance);
2624
2297
  const location = useLocation();
2625
2298
  const suffix = locationSuffix(location);
2626
2299
 
2627
2300
  return (
2628
- <main className="${tw('min-h-screen bg-um-canvas px-4 py-5 text-um-foreground sm:px-6 lg:px-12')}" data-mf-page-boundary={boundary}>
2301
+ <main className="${tw('min-h-screen bg-um-canvas px-4 py-5 text-um-foreground sm:px-6 lg:px-12')}">
2629
2302
  <div className="${tw('mx-auto flex min-h-20 max-w-7xl flex-col items-start gap-3 bg-white/90 px-4 py-3 shadow-xl shadow-stone-900/10 sm:px-6 md:flex-row md:flex-wrap md:items-center md:justify-between')}">
2630
2303
  <Header />
2631
2304
  <div className="${tw('flex min-w-0 flex-wrap items-center gap-2 md:ml-auto')}">
@@ -2654,204 +2327,33 @@ export default function ShellFrame({ boundary = 'explore', children, showCart =
2654
2327
  🇨🇿
2655
2328
  </option>
2656
2329
  </select>
2657
- {showCart ? <MiniCart /> : null}
2330
+ <StatusBadge />
2658
2331
  </div>
2659
2332
  </div>
2660
- <BoundaryOverlay />
2661
2333
  {children}
2662
2334
  </main>
2663
2335
  );
2664
2336
  }
2665
2337
  `;
2666
2338
  }
2667
- function createShellBoundaryOverlay() {
2668
- const tw = createTw(tailwindPrefixForApp(shellApp));
2669
- return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2670
- import { useEffect, useMemo, useState, type CSSProperties } from 'react';
2671
-
2672
- type BoundaryConfig = {
2673
- color: string;
2674
- label: string;
2675
- };
2676
-
2677
- type BoundaryBox = BoundaryConfig & {
2678
- height: number;
2679
- id: string;
2680
- labelPlacement: 'above' | 'edge' | 'inside';
2681
- left: number;
2682
- top: number;
2683
- width: number;
2684
- };
2685
-
2686
- declare global {
2687
- interface Window {
2688
- __ULTRAMODERN_BOUNDARIES__?: Partial<Record<string, Partial<BoundaryConfig>>>;
2689
- }
2690
- }
2691
-
2692
- const defaultBoundaryColors = {
2693
- checkout: 'var(--um-boundary-checkout, #f6cf45)',
2694
- decide: 'var(--um-boundary-decide, #30e27a)',
2695
- explore: 'var(--um-boundary-explore, #ff5a5f)',
2696
- } as const;
2697
-
2698
- const boundaryIds = ['explore', 'decide', 'checkout'] as const;
2699
-
2700
- export default function BoundaryOverlay() {
2701
- const { i18nInstance, language } = useModernI18n();
2702
- const [mounted, setMounted] = useState(false);
2703
- const [enabled, setEnabled] = useState(false);
2704
- const [boxes, setBoxes] = useState<BoundaryBox[]>([]);
2705
- const boundaryConfig = useMemo(() => {
2706
- const t = i18nInstance['t'].bind(i18nInstance);
2707
- const runtimeOverrides =
2708
- typeof window === 'undefined'
2709
- ? {}
2710
- : (window.__ULTRAMODERN_BOUNDARIES__ ?? {});
2711
-
2712
- return Object.fromEntries(
2713
- boundaryIds.map(id => [
2714
- id,
2715
- {
2716
- color: runtimeOverrides[id]?.color ?? defaultBoundaryColors[id],
2717
- label: runtimeOverrides[id]?.label ?? t(\`shell.boundaries.\${id}\`),
2718
- },
2719
- ]),
2720
- ) as Record<string, BoundaryConfig>;
2721
- }, [i18nInstance, language]);
2722
- const toggleLabel = i18nInstance['t'].bind(i18nInstance)(
2723
- 'shell.boundaries.toggle',
2724
- );
2725
-
2726
- useEffect(() => {
2727
- setMounted(true);
2728
- }, []);
2729
-
2730
- useEffect(() => {
2731
- if (!enabled) {
2732
- setBoxes([]);
2733
- return;
2734
- }
2735
-
2736
- const readBoxes = () => {
2737
- const nextBoxes = Array.from(
2738
- document.querySelectorAll<HTMLElement>(
2739
- '[data-mf-page-boundary], [data-mf-boundary]',
2740
- ),
2741
- )
2742
- .map((element, index) => {
2743
- const pageBoundary = element.dataset.mfPageBoundary;
2744
- const id = pageBoundary ?? element.dataset.mfBoundary ?? 'unknown';
2745
- const rect = element.getBoundingClientRect();
2746
- if (rect.width <= 0 || rect.height <= 0) {
2747
- return undefined;
2748
- }
2749
- const fallback = {
2750
- color: 'var(--um-boundary-unknown, #7c8cff)',
2751
- label: id,
2752
- };
2753
- const config = boundaryConfig[id] ?? fallback;
2754
-
2755
- return {
2756
- ...config,
2757
- height: rect.height,
2758
- id: \`\${id}-\${index}\`,
2759
- labelPlacement: pageBoundary ? 'edge' : rect.height < 48 ? 'above' : 'inside',
2760
- left: rect.left,
2761
- top: rect.top,
2762
- width: rect.width,
2763
- } satisfies BoundaryBox;
2764
- })
2765
- .filter((box): box is BoundaryBox => box !== undefined);
2766
-
2767
- setBoxes(nextBoxes);
2768
- };
2769
-
2770
- readBoxes();
2771
-
2772
- const resizeObserver = new ResizeObserver(readBoxes);
2773
- for (const element of document.querySelectorAll<HTMLElement>(
2774
- '[data-mf-page-boundary], [data-mf-boundary]',
2775
- )) {
2776
- resizeObserver.observe(element);
2777
- }
2778
-
2779
- const mutationObserver = new MutationObserver(readBoxes);
2780
- mutationObserver.observe(document.body, {
2781
- childList: true,
2782
- subtree: true,
2783
- });
2784
-
2785
- window.addEventListener('resize', readBoxes);
2786
- window.addEventListener('scroll', readBoxes, true);
2787
-
2788
- return () => {
2789
- mutationObserver.disconnect();
2790
- resizeObserver.disconnect();
2791
- window.removeEventListener('resize', readBoxes);
2792
- window.removeEventListener('scroll', readBoxes, true);
2793
- };
2794
- }, [boundaryConfig, enabled]);
2795
-
2796
- if (!mounted) {
2797
- return null;
2798
- }
2799
-
2800
- return (
2801
- <>
2802
- <label className="${tw('fixed bottom-5 left-5 z-[80] flex items-center gap-2 rounded-xl border border-stone-900/10 bg-white/95 px-4 py-3 text-sm font-semibold text-stone-950 shadow-2xl shadow-stone-900/15')}">
2803
- <input
2804
- className="${tw('size-4 accent-emerald-800')}"
2805
- checked={enabled}
2806
- onChange={event => setEnabled(event.currentTarget.checked)}
2807
- type="checkbox"
2808
- />
2809
- <span>{toggleLabel}</span>
2810
- </label>
2811
- {enabled ? (
2812
- <div aria-hidden="true" className="${tw('pointer-events-none fixed inset-0 z-[70]')}">
2813
- {boxes.map(box => (
2814
- <div
2815
- className="${tw('fixed rounded-lg border-2')}"
2816
- data-label-placement={box.labelPlacement}
2817
- key={box.id}
2818
- style={
2819
- {
2820
- borderColor: box.color,
2821
- boxShadow: \`0 0 0 1px rgba(255,255,255,.72), 0 6px 20px color-mix(in srgb, \${box.color} 20%, transparent)\`,
2822
- height: box.height,
2823
- left: box.left,
2824
- top: box.top,
2825
- width: box.width,
2826
- } as CSSProperties
2827
- }
2828
- >
2829
- <span
2830
- className={\`${tw('absolute whitespace-nowrap rounded-full px-2 py-1 text-[0.7rem] font-black leading-none text-stone-950')} \${box.labelPlacement === 'above' ? '${tw('bottom-[calc(100%+0.25rem)] right-1 top-auto')}' : box.labelPlacement === 'edge' ? '${tw('left-0 top-28 -translate-x-[calc(100%-1px)] -rotate-90 rounded-b-none')}' : '${tw('right-1 top-1')}'}\`}
2831
- style={{ backgroundColor: box.color }}
2832
- >
2833
- {box.label}
2834
- </span>
2835
- </div>
2836
- ))}
2837
- </div>
2838
- ) : null}
2839
- </>
2840
- );
2841
- }
2842
- `;
2843
- }
2844
- function createShellRemoteComponents(scope) {
2339
+ function createShellRemoteComponents(scope, remotes = []) {
2845
2340
  const tw = createTw(tailwindPrefixForApp(shellApp));
2341
+ const widgetRemotes = remotes.filter((remote)=>Object.hasOwn(remote.exposes ?? {}, './Widget'));
2342
+ const serverImports = widgetRemotes.map((remote)=>`import ${toPascalCase(remote.id)}WidgetServer from '${ultramodern_workspace_packageName(scope, remote.packageSuffix)}/Widget';`).join('\n');
2343
+ const hydratedExports = widgetRemotes.map((remote)=>{
2344
+ const componentName = `${toPascalCase(remote.id)}Widget`;
2345
+ return `const ${componentName} = createHydratedRemote(${componentName}Server, '${remoteDependencyAlias(remote)}/Widget');`;
2346
+ }).join('\n');
2347
+ const showcaseItems = widgetRemotes.map((remote)=>{
2348
+ const componentName = `${toPascalCase(remote.id)}Widget`;
2349
+ return ` <${componentName} key="${remote.id}" />`;
2350
+ }).join('\n');
2351
+ const remoteCount = String(widgetRemotes.length);
2846
2352
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
2847
2353
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
2848
2354
  import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
2849
- import HeaderServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Header';
2850
- import StorePickerServer from '${ultramodern_workspace_packageName(scope, 'explore')}/StorePicker';
2851
- import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Recommendations';
2852
- import ProductPageServer from '${ultramodern_workspace_packageName(scope, 'decide')}/ProductPage';
2853
- import MiniCartServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/MiniCart';
2854
- import CartPageServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/CartPage';
2355
+ import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2356
+ ${serverImports}
2855
2357
 
2856
2358
  type RemoteComponentModule = {
2857
2359
  default: ComponentType;
@@ -2866,8 +2368,11 @@ const loadRemoteComponent = async (specifier: string) => {
2866
2368
  };
2867
2369
 
2868
2370
  const remoteFallback =
2869
- ({ error }: { error: Error }) =>
2870
- <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>Vertical unavailable</div>;
2371
+ ({ error }: { error: Error }) => {
2372
+ const { i18nInstance } = useModernI18n();
2373
+ const t = i18nInstance['t'].bind(i18nInstance);
2374
+ return <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>{t('shell.remoteUnavailable')}</div>;
2375
+ };
2871
2376
 
2872
2377
  const createHydratedRemote = (
2873
2378
  ServerComponent: ComponentType,
@@ -2909,12 +2414,50 @@ const createHydratedRemote = (
2909
2414
  };
2910
2415
  };
2911
2416
 
2912
- export const Header = createHydratedRemote(HeaderServer, 'explore/Header');
2913
- export const StorePicker = createHydratedRemote(StorePickerServer, 'explore/StorePicker');
2914
- export const Recommendations = createHydratedRemote(RecommendationsServer, 'explore/Recommendations');
2915
- export const ProductPage = createHydratedRemote(ProductPageServer, 'decide/ProductPage');
2916
- export const MiniCart = createHydratedRemote(MiniCartServer, 'checkout/MiniCart');
2917
- export const CartPage = createHydratedRemote(CartPageServer, 'checkout/CartPage');
2417
+ ${hydratedExports}
2418
+
2419
+ export function Header() {
2420
+ const { i18nInstance } = useModernI18n();
2421
+ const t = i18nInstance['t'].bind(i18nInstance);
2422
+
2423
+ return (
2424
+ <header className="${tw('flex min-w-0 flex-wrap items-center gap-x-8 gap-y-2 md:flex-1')}" data-modern-boundary-id="${shellApp.mfName}" data-modern-mf-expose="shell/Header">
2425
+ <I18nLink className="${tw('whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline')}" to="/">{t('shell.title')}</I18nLink>
2426
+ </header>
2427
+ );
2428
+ }
2429
+
2430
+ export function StatusBadge() {
2431
+ const { i18nInstance } = useModernI18n();
2432
+ const t = i18nInstance['t'].bind(i18nInstance);
2433
+
2434
+ return (
2435
+ <span className="${tw('inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 shadow-lg shadow-stone-900/5')}">
2436
+ {${remoteCount}} {t('shell.hero.cardOneKicker')}
2437
+ </span>
2438
+ );
2439
+ }
2440
+
2441
+ export function VerticalShowcase() {
2442
+ const { i18nInstance } = useModernI18n();
2443
+ const t = i18nInstance['t'].bind(i18nInstance);
2444
+
2445
+ if (${remoteCount} === 0) {
2446
+ return (
2447
+ <section className="${tw('mx-auto mt-12 max-w-7xl rounded-2xl bg-white/90 p-6 shadow-xl shadow-stone-900/10')}">
2448
+ <p className="${tw('text-lg font-bold text-stone-700')}">{t('shell.hero.empty')}</p>
2449
+ </section>
2450
+ );
2451
+ }
2452
+
2453
+ return (
2454
+ <section className="${tw('mx-auto mt-12 max-w-7xl')}" data-modern-boundary-id="${shellApp.mfName}">
2455
+ <div className="${tw('grid gap-4 md:grid-cols-2')}">
2456
+ ${showcaseItems}
2457
+ </div>
2458
+ </section>
2459
+ );
2460
+ }
2918
2461
  `;
2919
2462
  }
2920
2463
  function createRemotePage(app) {
@@ -2999,15 +2542,21 @@ export default function Layout() {
2999
2542
  }
3000
2543
  function createRemoteEntry(app) {
3001
2544
  const tw = createTw(tailwindPrefixForApp(app));
3002
- if (app.exposes?.['./ProductPage']) return `export { default } from './components/product-page';
2545
+ const domain = app.domain ?? app.id;
2546
+ if (app.exposes?.['./RecordPage']) return `export { default } from './components/record-page';
3003
2547
  `;
3004
- if (app.exposes?.['./CartPage']) return `export { default } from './components/cart-page';
2548
+ if (app.exposes?.['./ActionQueue']) return `export { default } from './components/action-queue';
3005
2549
  `;
3006
- return `export default function ${toPascalCase(app.domain ?? app.id)}Route() {
2550
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2551
+
2552
+ export default function ${toPascalCase(domain)}Route() {
2553
+ const { i18nInstance } = useModernI18n();
2554
+ const t = i18nInstance['t'].bind(i18nInstance);
2555
+
3007
2556
  return (
3008
2557
  <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}" data-mf-expose="./Route">
3009
- <h2 className="${tw('text-2xl font-black')}">${app.displayName}</h2>
3010
- <p className="${tw('mt-2 text-stone-600')}">Route surface for ${app.domain ?? app.id}.</p>
2558
+ <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2559
+ <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.routeSurface')}</p>
3011
2560
  </section>
3012
2561
  );
3013
2562
  }
@@ -3015,13 +2564,18 @@ function createRemoteEntry(app) {
3015
2564
  }
3016
2565
  function createRemoteWidget(app) {
3017
2566
  const tw = createTw(tailwindPrefixForApp(app));
3018
- const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
3019
- const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
3020
- return `export default function ${componentName}() {
2567
+ const domain = app.domain ?? app.id;
2568
+ const componentName = `${toPascalCase(domain)}Widget`;
2569
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2570
+
2571
+ export default function ${componentName}() {
2572
+ const { i18nInstance } = useModernI18n();
2573
+ const t = i18nInstance['t'].bind(i18nInstance);
2574
+
3021
2575
  return (
3022
2576
  <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}">
3023
- <h2 className="${tw('text-2xl font-black')}">${app.displayName}</h2>
3024
- <p className="${tw('mt-2 text-stone-600')}">${body}</p>
2577
+ <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2578
+ <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.widgetBody')}</p>
3025
2579
  </section>
3026
2580
  );
3027
2581
  }
@@ -3029,90 +2583,88 @@ function createRemoteWidget(app) {
3029
2583
  }
3030
2584
  function createRemoteExposeComponent(app, expose) {
3031
2585
  const tw = createTw(tailwindPrefixForApp(app));
3032
- if ('explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2586
+ if ('workspace' === app.id && './Header' === expose) return `import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
3033
2587
 
3034
2588
  export default function Header() {
3035
- const { i18nInstance, language } = useModernI18n();
2589
+ const { i18nInstance } = useModernI18n();
3036
2590
  const t = i18nInstance['t'].bind(i18nInstance);
3037
2591
 
3038
2592
  return (
3039
- <header className="${tw('flex min-w-0 flex-wrap items-center gap-x-8 gap-y-2 md:flex-1')}" data-mf-boundary="explore">
3040
- <a className="${tw('whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline')}" href={\`/\${language}\`}>Acre & Iron</a>
3041
- <nav aria-label={t('explore.header.navigation')} className="${tw('flex items-center gap-5')}">
3042
- <a className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
3043
- <a className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
2593
+ <header className="${tw('flex min-w-0 flex-wrap items-center gap-x-8 gap-y-2 md:flex-1')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2594
+ <I18nLink className="${tw('whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline')}" to="/">{t('workspace.header.brand')}</I18nLink>
2595
+ <nav aria-label={t('workspace.header.navigation')} className="${tw('flex items-center gap-5')}">
2596
+ <I18nLink className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" to="/workspaces">{t('workspace.header.workspaces')}</I18nLink>
2597
+ <I18nLink className="${tw('text-sm font-extrabold text-stone-900 no-underline')}" to="/directory">{t('workspace.header.directory')}</I18nLink>
3044
2598
  </nav>
3045
2599
  </header>
3046
2600
  );
3047
2601
  }
3048
2602
  `;
3049
- if ('explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2603
+ if ('workspace' === app.id && './Highlights' === expose) return `import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
3050
2604
 
3051
- const tractors = [
3052
- { badge: 'explore.recommendations.bestRows', image: '${commerceAssetUrl('orchard.svg')}', name: 'Orchard Tractor', slug: 'orchard-tractor' },
3053
- { badge: 'explore.recommendations.aiFirst', image: '${commerceAssetUrl('autonomy.svg')}', name: 'Autonomy Retrofit Kit', slug: 'autonomy-retrofit-kit' },
3054
- { badge: 'explore.recommendations.loaderReady', image: '${commerceAssetUrl('field-loader.svg')}', name: 'Field Loader 112', slug: 'field-loader-112' },
3055
- { badge: 'explore.recommendations.vineyard', image: '${commerceAssetUrl('vineyard.svg')}', name: 'Vineyard Narrow 80', slug: 'vineyard-narrow-80' },
2605
+ const highlights = [
2606
+ { badge: 'workspace.highlights.shell', href: '/workspaces', name: 'workspace.highlights.shellTitle' },
2607
+ { badge: 'workspace.highlights.records', href: '/records/starter-record', name: 'workspace.highlights.recordsTitle' },
2608
+ { badge: 'workspace.highlights.actions', href: '/actions', name: 'workspace.highlights.actionsTitle' },
3056
2609
  ] as const;
3057
2610
 
3058
- export default function Recommendations() {
3059
- const { i18nInstance, language } = useModernI18n();
2611
+ export default function Highlights() {
2612
+ const { i18nInstance } = useModernI18n();
3060
2613
  const t = i18nInstance['t'].bind(i18nInstance);
3061
2614
 
3062
2615
  return (
3063
- <section className="${tw('mx-auto mt-12 max-w-7xl')}" data-mf-boundary="explore">
3064
- <h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('explore.recommendations.title')}</h2>
3065
- <div className="${tw('mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-4')}">
3066
- {tractors.map(tractor => (
3067
- <a className="${tw('block rounded-2xl bg-white/90 p-4 text-stone-950 no-underline shadow-xl shadow-stone-900/10 transition hover:-translate-y-0.5 hover:shadow-2xl')}" href={\`/\${language}/tractors/\${tractor.slug}\`} key={tractor.slug}>
3068
- <img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={tractor.image} />
3069
- <span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-amber-700')}">{t(tractor.badge)}</span>
3070
- <strong className="${tw('mt-2 block text-xl font-black leading-tight')}">{tractor.name}</strong>
3071
- </a>
2616
+ <section className="${tw('mx-auto mt-12 max-w-7xl')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2617
+ <h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('workspace.highlights.title')}</h2>
2618
+ <div className="${tw('mt-5 grid gap-4 md:grid-cols-3')}">
2619
+ {highlights.map(highlight => (
2620
+ <I18nLink className="${tw('block rounded-2xl bg-white/90 p-5 text-stone-950 no-underline shadow-xl shadow-stone-900/10 transition hover:-translate-y-0.5 hover:shadow-2xl')}" key={highlight.href} to={highlight.href}>
2621
+ <span className="${tw('text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t(highlight.badge)}</span>
2622
+ <strong className="${tw('mt-3 block text-xl font-black leading-tight')}">{t(highlight.name)}</strong>
2623
+ </I18nLink>
3072
2624
  ))}
3073
2625
  </div>
3074
2626
  </section>
3075
2627
  );
3076
2628
  }
3077
2629
  `;
3078
- if ('explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3079
-
3080
- const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
3081
- const vineyardImage = '${commerceAssetUrl('vineyard.svg')}';
2630
+ if ('workspace' === app.id && './DirectoryPanel' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3082
2631
 
3083
- export default function StorePicker() {
2632
+ export default function DirectoryPanel() {
3084
2633
  const { i18nInstance } = useModernI18n();
3085
2634
  const t = i18nInstance['t'].bind(i18nInstance);
3086
2635
 
3087
2636
  return (
3088
- <section className="${tw('mx-auto mt-12 max-w-7xl')}" data-mf-boundary="explore">
3089
- <h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('explore.stores.title')}</h2>
2637
+ <section className="${tw('mx-auto mt-12 max-w-7xl')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2638
+ <h2 className="${tw('text-3xl font-black tracking-normal text-stone-950')}">{t('workspace.directory.title')}</h2>
3090
2639
  <div className="${tw('mt-5 grid gap-4 md:grid-cols-2')}">
3091
- <article className="${tw('rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10')}">
3092
- <img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={fieldLoaderImage} />
3093
- <span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('explore.stores.northRegion')}</span>
3094
- <strong className="${tw('mt-2 block text-2xl font-black')}">Bohemia Field Supply</strong>
2640
+ <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
2641
+ <span className="${tw('text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('workspace.directory.platformTeam')}</span>
2642
+ <strong className="${tw('mt-2 block text-2xl font-black')}">{t('workspace.directory.platformName')}</strong>
2643
+ <p className="${tw('mt-2 text-sm font-semibold text-stone-600')}">{t('workspace.directory.platformCopy')}</p>
3095
2644
  </article>
3096
- <article className="${tw('rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10')}">
3097
- <img alt="" className="${tw('aspect-video w-full rounded-xl bg-stone-200 object-cover')}" src={vineyardImage} />
3098
- <span className="${tw('mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('explore.stores.southRegion')}</span>
3099
- <strong className="${tw('mt-2 block text-2xl font-black')}">Moravia Iron Works</strong>
2645
+ <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
2646
+ <span className="${tw('text-xs font-black uppercase tracking-[0.16em] text-emerald-800')}">{t('workspace.directory.deliveryTeam')}</span>
2647
+ <strong className="${tw('mt-2 block text-2xl font-black')}">{t('workspace.directory.deliveryName')}</strong>
2648
+ <p className="${tw('mt-2 text-sm font-semibold text-stone-600')}">{t('workspace.directory.deliveryCopy')}</p>
3100
2649
  </article>
3101
2650
  </div>
3102
2651
  </section>
3103
2652
  );
3104
2653
  }
3105
2654
  `;
3106
- if ('explore' === app.id && './Footer' === expose) return `export default function Footer() {
3107
- return <footer className="${tw('mx-auto mt-12 max-w-7xl text-sm font-bold text-stone-600')}" data-mf-boundary="explore">Acre & Iron</footer>;
2655
+ if ('workspace' === app.id && './Footer' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2656
+
2657
+ export default function Footer() {
2658
+ const { i18nInstance } = useModernI18n();
2659
+ const t = i18nInstance['t'].bind(i18nInstance);
2660
+
2661
+ return <footer className="${tw('mx-auto mt-12 max-w-7xl text-sm font-bold text-stone-600')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">{t('workspace.footer')}</footer>;
3108
2662
  }
3109
2663
  `;
3110
2664
  if ('./Widget' === expose) return createRemoteWidget(app);
3111
2665
  const componentName = `${toPascalCase(app.domain ?? app.id)}${toPascalCase(expose.replace(/^\.\//u, ''))}`;
3112
- if ('decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3113
- import { AddToCart, Recommendations } from './vertical-components';
3114
-
3115
- const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
2666
+ if ('records' === app.id && './RecordPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2667
+ import { Highlights, StartAction } from './vertical-components';
3116
2668
 
3117
2669
  export default function ${componentName}() {
3118
2670
  const { i18nInstance } = useModernI18n();
@@ -3120,94 +2672,98 @@ export default function ${componentName}() {
3120
2672
 
3121
2673
  return (
3122
2674
  <>
3123
- <section className="${tw('mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14')}" data-mf-boundary="decide" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3124
- <img alt="" className="${tw('aspect-[1/0.9] w-full rounded-3xl border-[18px] border-amber-200 bg-stone-200 object-cover shadow-2xl shadow-stone-900/20')}" src={fieldLoaderImage} />
2675
+ <section className="${tw('mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2676
+ <div className="${tw('rounded-3xl border-[18px] border-amber-200 bg-white/90 p-8 shadow-2xl shadow-stone-900/15')}">
2677
+ <p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('records.record.lifecycle')}</p>
2678
+ <dl className="${tw('mt-6 grid gap-4')}">
2679
+ <div><dt className="${tw('text-sm font-bold text-stone-500')}">{t('records.record.owner')}</dt><dd className="${tw('text-xl font-black')}">{t('records.record.ownerName')}</dd></div>
2680
+ <div><dt className="${tw('text-sm font-bold text-stone-500')}">{t('records.record.state')}</dt><dd className="${tw('text-xl font-black')}">{t('records.record.ready')}</dd></div>
2681
+ </dl>
2682
+ </div>
3125
2683
  <div>
3126
- <p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('decide.product.eyebrow')}</p>
3127
- <h1 className="${tw('mt-3 text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">Field Loader 112</h1>
3128
- <p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('decide.product.lede')}</p>
2684
+ <p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('records.record.eyebrow')}</p>
2685
+ <h1 className="${tw('mt-3 text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('records.record.title')}</h1>
2686
+ <p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('records.record.lede')}</p>
3129
2687
  <div className="${tw('mt-8 grid gap-4 sm:grid-cols-3')}">
3130
- <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.price')}</span><strong className="${tw('mt-2 block text-lg font-black')}">EUR 42,500</strong></article>
3131
- <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.power')}</span><strong className="${tw('mt-2 block text-lg font-black')}">112 hp</strong></article>
3132
- <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('decide.product.availability')}</span><strong className="${tw('mt-2 block text-lg font-black')}">{t('decide.product.inStock')}</strong></article>
2688
+ <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('records.record.priority')}</span><strong className="${tw('mt-2 block text-lg font-black')}">{t('records.record.priorityValue')}</strong></article>
2689
+ <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('records.record.sla')}</span><strong className="${tw('mt-2 block text-lg font-black')}">{t('records.record.slaValue')}</strong></article>
2690
+ <article className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}"><span className="${tw('block text-sm font-bold text-stone-500')}">{t('records.record.status')}</span><strong className="${tw('mt-2 block text-lg font-black')}">{t('records.record.ready')}</strong></article>
3133
2691
  </div>
3134
- <AddToCart />
2692
+ <StartAction />
3135
2693
  </div>
3136
2694
  </section>
3137
- <Recommendations />
2695
+ <Highlights />
3138
2696
  </>
3139
2697
  );
3140
2698
  }
3141
2699
  `;
3142
- if ('checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3143
- import { useCartLines } from '../cart-store';
2700
+ if ('actions' === app.id && './StartAction' === expose) return `import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2701
+ import { useActionQueue } from '../action-queue-store';
3144
2702
 
3145
2703
  export default function ${componentName}() {
3146
- const { i18nInstance, language } = useModernI18n();
2704
+ const { i18nInstance } = useModernI18n();
3147
2705
  const t = i18nInstance['t'].bind(i18nInstance);
3148
- const cart = useCartLines();
2706
+ const queue = useActionQueue();
3149
2707
 
3150
2708
  return (
3151
- <div className="${tw('mt-8 flex flex-wrap gap-3')}" data-mf-boundary="checkout">
3152
- <button className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" onClick={cart.addFieldLoader} type="button">
3153
- {t('checkout.actions.addToCart')}
2709
+ <div className="${tw('mt-8 flex flex-wrap gap-3')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2710
+ <button className="${tw('inline-flex min-h-11 items-center justify-center rounded-full bg-emerald-800 px-5 font-bold text-white shadow-lg shadow-stone-900/10')}" onClick={queue.addStarterAction} type="button">
2711
+ {t('actions.controls.start')}
3154
2712
  </button>
3155
- <a className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}" href={\`/\${language}/cart\`}>
3156
- {t('checkout.actions.viewCart')}
3157
- </a>
2713
+ <I18nLink className="${tw('inline-flex min-h-11 items-center justify-center rounded-full border border-stone-900/15 bg-white/90 px-5 font-bold text-stone-950 shadow-lg shadow-stone-900/10')}" to="/actions">
2714
+ {t('actions.controls.viewQueue')}
2715
+ </I18nLink>
3158
2716
  </div>
3159
2717
  );
3160
2718
  }
3161
2719
  `;
3162
- if ('checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3163
- import { useCartLines } from '../cart-store';
2720
+ if ('actions' === app.id && './StatusBadge' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2721
+ import { useActionQueue } from '../action-queue-store';
3164
2722
 
3165
2723
  export default function ${componentName}() {
3166
- const { i18nInstance, language } = useModernI18n();
2724
+ const { i18nInstance } = useModernI18n();
3167
2725
  const t = i18nInstance['t'].bind(i18nInstance);
3168
- const cart = useCartLines();
3169
- const count = cart.lines.reduce((sum, line) => sum + line.quantity, 0);
2726
+ const queue = useActionQueue();
3170
2727
 
3171
2728
  return (
3172
- <a className="${tw('inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 no-underline shadow-lg shadow-stone-900/5')}" data-mf-boundary="checkout" href={\`/\${language}/cart\`}>
3173
- {t('checkout.cart.title')} ({count})
3174
- </a>
2729
+ <span className="${tw('inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 shadow-lg shadow-stone-900/5')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2730
+ {t('actions.queue.itemCount', { count: queue.lines.length })}
2731
+ </span>
3175
2732
  );
3176
2733
  }
3177
2734
  `;
3178
- if ('checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3179
- import { useCartLines } from '../cart-store';
2735
+ if ('actions' === app.id && './ActionQueue' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2736
+ import { useActionQueue } from '../action-queue-store';
3180
2737
 
3181
2738
  export default function ${componentName}() {
3182
2739
  const { i18nInstance } = useModernI18n();
3183
2740
  const t = i18nInstance['t'].bind(i18nInstance);
3184
- const cart = useCartLines();
2741
+ const queue = useActionQueue();
3185
2742
 
3186
2743
  return (
3187
- <section className="${tw('mx-auto mt-10 max-w-7xl')}" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3188
- <h1 className="${tw('text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('checkout.cart.title')}</h1>
2744
+ <section className="${tw('mx-auto mt-10 max-w-7xl')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2745
+ <h1 className="${tw('text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('actions.queue.title')}</h1>
3189
2746
  <div className="${tw('mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
3190
- {cart.lines.length === 0 ? (
3191
- <p>{t('checkout.cart.empty')}</p>
2747
+ {queue.lines.length === 0 ? (
2748
+ <p>{t('actions.queue.empty')}</p>
3192
2749
  ) : (
3193
2750
  <>
3194
- {cart.lines.map(line => (
2751
+ {queue.lines.map(line => (
3195
2752
  <article className="${tw('grid gap-4 border-t border-stone-900/10 py-4 first:border-t-0 sm:grid-cols-[1fr_auto] sm:items-center')}" key={line.id}>
3196
2753
  <div>
3197
- <strong className="${tw('text-lg font-black')}">{line.name}</strong>
3198
- <p className="${tw('text-stone-600')}">EUR {line.price.toLocaleString('en-US')}</p>
2754
+ <strong className="${tw('text-lg font-black')}">{t(line.nameKey)}</strong>
2755
+ <p className="${tw('text-stone-600')}">{t(\`actions.queue.status.\${line.status}\`)}</p>
3199
2756
  </div>
3200
2757
  <div className="${tw('flex flex-wrap items-center gap-2')}">
3201
- <button className="${tw('inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black')}" onClick={() => cart.decrement(line.id)} type="button">-</button>
3202
- <span className="${tw('min-w-6 text-center font-black')}">{line.quantity}</span>
3203
- <button className="${tw('inline-flex size-9 items-center justify-center rounded-full border border-stone-900/15 bg-white font-black')}" onClick={() => cart.increment(line.id)} type="button">+</button>
3204
- <button className="${tw('inline-flex min-h-10 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 font-bold text-stone-950')}" onClick={() => cart.remove(line.id)} type="button">
3205
- {t('checkout.actions.remove')}
2758
+ <button className="${tw('inline-flex min-h-10 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 font-bold text-stone-950')}" onClick={() => queue.complete(line.id)} type="button">
2759
+ {t('actions.controls.complete')}
2760
+ </button>
2761
+ <button className="${tw('inline-flex min-h-10 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 font-bold text-stone-950')}" onClick={() => queue.remove(line.id)} type="button">
2762
+ {t('actions.controls.remove')}
3206
2763
  </button>
3207
2764
  </div>
3208
2765
  </article>
3209
2766
  ))}
3210
- <p><strong>{t('checkout.cart.total')}: EUR {cart.total.toLocaleString('en-US')}</strong></p>
3211
2767
  </>
3212
2768
  )}
3213
2769
  </div>
@@ -3215,23 +2771,34 @@ export default function ${componentName}() {
3215
2771
  );
3216
2772
  }
3217
2773
  `;
3218
- return `export default function ${componentName}() {
2774
+ if ('actions' === app.id && './ActionReviewPage' === expose) return `export { default } from './action-queue';
2775
+ `;
2776
+ if ('actions' === app.id && './ActionSuccessPage' === expose) return `export { default } from './action-queue';
2777
+ `;
2778
+ const domain = app.domain ?? app.id;
2779
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2780
+
2781
+ export default function ${componentName}() {
2782
+ const { i18nInstance } = useModernI18n();
2783
+ const t = i18nInstance['t'].bind(i18nInstance);
2784
+
3219
2785
  return (
3220
- <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3221
- <h2 className="${tw('text-2xl font-black')}">${app.displayName} ${expose.replace(/^\.\//u, '')}</h2>
3222
- <p className="${tw('mt-2 text-stone-600')}">Module Federation surface owned by ${app.ownership.team}.</p>
2786
+ <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2787
+ <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2788
+ <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.federatedSurface')}</p>
3223
2789
  </section>
3224
2790
  );
3225
2791
  }
3226
2792
  `;
3227
2793
  }
3228
- function createDecideRemoteComponents(scope, app) {
2794
+ function createRecordsRemoteComponents(scope, app) {
3229
2795
  const tw = createTw(tailwindPrefixForApp(app));
3230
2796
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
3231
2797
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
3232
2798
  import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
3233
- import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Recommendations';
3234
- import AddToCartServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/AddToCart';
2799
+ import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2800
+ import HighlightsServer from '${ultramodern_workspace_packageName(scope, 'workspace')}/Highlights';
2801
+ import StartActionServer from '${ultramodern_workspace_packageName(scope, 'actions')}/StartAction';
3235
2802
 
3236
2803
  type RemoteComponentModule = {
3237
2804
  default: ComponentType;
@@ -3246,8 +2813,11 @@ const loadRemoteComponent = async (specifier: string) => {
3246
2813
  };
3247
2814
 
3248
2815
  const remoteFallback =
3249
- ({ error }: { error: Error }) =>
3250
- <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>Vertical unavailable</div>;
2816
+ ({ error }: { error: Error }) => {
2817
+ const { i18nInstance } = useModernI18n();
2818
+ const t = i18nInstance['t'].bind(i18nInstance);
2819
+ return <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>{t('records.remoteUnavailable')}</div>;
2820
+ };
3251
2821
 
3252
2822
  const createHydratedRemote = (
3253
2823
  ServerComponent: ComponentType,
@@ -3289,8 +2859,8 @@ const createHydratedRemote = (
3289
2859
  };
3290
2860
  };
3291
2861
 
3292
- export const AddToCart = createHydratedRemote(AddToCartServer, 'checkout/AddToCart');
3293
- export const Recommendations = createHydratedRemote(RecommendationsServer, 'explore/Recommendations');
2862
+ export const Highlights = createHydratedRemote(HighlightsServer, 'workspace/Highlights');
2863
+ export const StartAction = createHydratedRemote(StartActionServer, 'actions/StartAction');
3294
2864
  `;
3295
2865
  }
3296
2866
  function remoteComponentOutputPath(app, expose) {
@@ -3298,216 +2868,345 @@ function remoteComponentOutputPath(app, expose) {
3298
2868
  if (!exposePath?.startsWith('./src/components/')) return;
3299
2869
  return `${app.directory}/${exposePath.replace(/^\.\//u, '')}`;
3300
2870
  }
3301
- function createAppLocaleMessages(app, language) {
3302
- const czechLabels = {
3303
- checkout: {
3304
- role: 'pokladna',
3305
- title: 'Pokladní remote'
2871
+ const commonLocaleMessages = {
2872
+ cs: {
2873
+ language: {
2874
+ cs: 'Čeština',
2875
+ en: 'Angličtina',
2876
+ switcher: 'Jazyk'
3306
2877
  },
3307
- decide: {
3308
- role: 'rozhodování',
3309
- title: 'Rozhodovací remote'
2878
+ routes: {
2879
+ actions: 'Akce',
2880
+ directory: 'Adresář',
2881
+ done: 'Akce dokončena',
2882
+ home: 'Domů',
2883
+ recordDetail: 'Detail záznamu',
2884
+ review: 'Revize akce',
2885
+ unavailable: 'Nedostupné',
2886
+ workspaces: 'Pracovní prostory'
2887
+ }
2888
+ },
2889
+ en: {
2890
+ language: {
2891
+ cs: 'Czech',
2892
+ en: 'English',
2893
+ switcher: 'Language'
3310
2894
  },
3311
- 'design-system': {
3312
- role: 'design system',
3313
- title: 'Design system remote'
2895
+ routes: {
2896
+ actions: 'Actions',
2897
+ directory: 'Directory',
2898
+ done: 'Action complete',
2899
+ home: 'Home',
2900
+ recordDetail: 'Record detail',
2901
+ review: 'Action review',
2902
+ unavailable: 'Unavailable',
2903
+ workspaces: 'Workspaces'
2904
+ }
2905
+ }
2906
+ };
2907
+ const generatedLocaleResources = {
2908
+ cs: {
2909
+ actions: {
2910
+ ...commonLocaleMessages.cs,
2911
+ federatedSurface: 'Federovaná plocha vlastněná tímto verticalem.',
2912
+ remoteUnavailable: 'Remote vertical je nedostupný',
2913
+ role: 'akce',
2914
+ routeSurface: 'Routovaná plocha vlastněná tímto verticalem.',
2915
+ title: 'Akční vertical',
2916
+ widgetBody: 'Vlastní routovanou plochu verticalu.',
2917
+ controls: {
2918
+ complete: 'Dokončit',
2919
+ remove: 'Odebrat',
2920
+ start: 'Spustit akci',
2921
+ viewQueue: 'Zobrazit frontu'
2922
+ },
2923
+ queue: {
2924
+ empty: 'Zatím nejsou ve frontě žádné akce.',
2925
+ itemCount_few: '{{count}} akce',
2926
+ itemCount_many: '{{count}} akce',
2927
+ itemCount_one: '{{count}} akce',
2928
+ itemCount_other: '{{count}} akcí',
2929
+ starterAction: 'Zkontrolovat startovací záznam',
2930
+ status: {
2931
+ complete: 'Dokončeno',
2932
+ queued: 'Ve frontě'
2933
+ },
2934
+ title: 'Fronta akcí'
2935
+ }
3314
2936
  },
3315
- explore: {
3316
- role: 'procházení',
3317
- title: 'Průzkumný remote'
2937
+ records: {
2938
+ ...commonLocaleMessages.cs,
2939
+ federatedSurface: 'Federovaná plocha vlastněná tímto verticalem.',
2940
+ remoteUnavailable: 'Remote vertical je nedostupný',
2941
+ role: 'záznamy',
2942
+ routeSurface: 'Routovaná plocha vlastněná tímto verticalem.',
2943
+ title: 'Záznamový vertical',
2944
+ widgetBody: 'Vlastní routovanou plochu verticalu.',
2945
+ record: {
2946
+ eyebrow: 'Detail záznamu',
2947
+ lede: 'Startovací záznam ověřuje spolupráci lokalizovaného SSR, hydratace remote části a Effect BFF vlastněného verticalem.',
2948
+ lifecycle: 'Životní cyklus',
2949
+ owner: 'Vlastník',
2950
+ ownerName: 'Zkušenost pracovního prostoru',
2951
+ priority: 'Priorita',
2952
+ priorityValue: 'P1',
2953
+ ready: 'Připraveno',
2954
+ sla: 'SLA',
2955
+ slaValue: '24 h',
2956
+ state: 'Stav',
2957
+ status: 'Status',
2958
+ title: 'Startovací záznam'
2959
+ }
3318
2960
  },
3319
- identity: {
3320
- role: 'identita',
3321
- title: 'Identitní remote'
3322
- }
3323
- };
3324
- if ('shell' === app.kind) return {
3325
2961
  shell: {
2962
+ boundaries: {
2963
+ toggle: 'zobrazit hranice verticalů'
2964
+ },
3326
2965
  hero: {
3327
- eyebrow: 'en' === language ? 'Federated tractor commerce' : 'Federovaný obchod s traktory',
3328
- lede: 'en' === language ? 'A full-stack Micro Vertical reference where Explore, Decide, and Checkout ship independently but compose into one storefront.' : 'Full-stack Micro Vertical ukázka, kde Procházení, Rozhodování a Pokladna vycházejí samostatně, ale skládají jeden obchod.',
3329
- primary: 'en' === language ? 'View Field Loader' : 'Zobrazit Field Loader',
3330
- secondary: 'en' === language ? 'Compare machines' : 'Porovnat stroje'
2966
+ cardOne: 'Přidejte první business vertical příkazem create <domain> --vertical, ho opravdu potřebujete.',
2967
+ cardOneKicker: 'Verticaly',
2968
+ cardTwo: 'Plný markup, styly a lokalizovaný obsah se vykreslí před hydratací.',
2969
+ cardTwoKicker: 'Vykreslení',
2970
+ empty: 'Zatím nejsou připojené žádné MicroVerticaly.',
2971
+ eyebrow: 'Shell SuperApp starter',
2972
+ lede: 'Začněte s produkčně připraveným shellem. MicroVerticaly přidávejte až podle skutečných business domén.',
2973
+ primary: 'Shell je připraven',
2974
+ secondary: 'Přidejte vertical, až bude potřeba'
2975
+ },
2976
+ language: commonLocaleMessages.cs.language,
2977
+ remoteUnavailable: 'Remote vertical je nedostupný',
2978
+ remotes: {},
2979
+ routes: {
2980
+ home: commonLocaleMessages.cs.routes.home
2981
+ },
2982
+ title: 'UltraModern Workspace'
2983
+ },
2984
+ workspace: {
2985
+ ...commonLocaleMessages.cs,
2986
+ federatedSurface: 'Federovaná plocha vlastněná tímto verticalem.',
2987
+ footer: 'UltraModern workspace',
2988
+ remoteUnavailable: 'Remote vertical je nedostupný',
2989
+ role: 'pracovní prostor',
2990
+ routeSurface: 'Routovaná plocha vlastněná tímto verticalem.',
2991
+ title: 'Pracovní vertical',
2992
+ widgetBody: 'Poskytuje sdílené UI prvky pro pracovní prostor.',
2993
+ directory: {
2994
+ deliveryCopy: 'Vlastní generované akční toky a stav workflow.',
2995
+ deliveryName: 'Doručovací operace',
2996
+ deliveryTeam: 'Doručovací tým',
2997
+ platformCopy: 'Vlastní skládání shellu, routování a sdílený zážitek.',
2998
+ platformName: 'Platformní zkušenost',
2999
+ platformTeam: 'Platformní tým',
3000
+ title: 'Adresář'
3331
3001
  },
3332
- language: {
3333
- cs: 'en' === language ? 'Czech' : 'Čeština',
3334
- en: 'en' === language ? 'English' : 'Angličtina',
3335
- switcher: 'en' === language ? 'Language' : 'Jazyk'
3002
+ header: {
3003
+ brand: 'UltraModern Workspace',
3004
+ directory: 'Adresář',
3005
+ navigation: 'Hlavní navigace',
3006
+ workspaces: 'Pracovní prostory'
3336
3007
  },
3337
- remotes: {
3338
- checkout: 'en' === language ? 'Checkout Vertical' : 'Checkout remote',
3339
- decide: 'en' === language ? 'Decide Vertical' : 'Decide remote',
3340
- explore: 'en' === language ? 'Explore Vertical' : 'Explore remote'
3008
+ highlights: {
3009
+ actions: 'Akční část',
3010
+ actionsTitle: 'Spusťte akci napříč verticaly',
3011
+ records: 'Záznamová část',
3012
+ recordsTitle: 'Otevřete záznam vlastněný routou',
3013
+ shell: 'Shell část',
3014
+ shellTitle: 'Skládejte verticaly v shellu',
3015
+ title: 'Generované plochy verticalů'
3016
+ }
3017
+ }
3018
+ },
3019
+ en: {
3020
+ actions: {
3021
+ ...commonLocaleMessages.en,
3022
+ federatedSurface: 'Federated surface owned by this vertical.',
3023
+ remoteUnavailable: 'Remote vertical unavailable',
3024
+ role: 'actions',
3025
+ routeSurface: 'Route surface owned by this vertical.',
3026
+ title: 'Actions Vertical',
3027
+ widgetBody: 'Owns a vertical route surface.',
3028
+ controls: {
3029
+ complete: 'Complete',
3030
+ remove: 'Remove',
3031
+ start: 'Start action',
3032
+ viewQueue: 'View queue'
3341
3033
  },
3034
+ queue: {
3035
+ empty: 'No actions are queued yet.',
3036
+ itemCount_one: '{{count}} action',
3037
+ itemCount_other: '{{count}} actions',
3038
+ starterAction: 'Review starter record',
3039
+ status: {
3040
+ complete: 'Complete',
3041
+ queued: 'Queued'
3042
+ },
3043
+ title: 'Action queue'
3044
+ }
3045
+ },
3046
+ records: {
3047
+ ...commonLocaleMessages.en,
3048
+ federatedSurface: 'Federated surface owned by this vertical.',
3049
+ remoteUnavailable: 'Remote vertical unavailable',
3050
+ role: 'records',
3051
+ routeSurface: 'Route surface owned by this vertical.',
3052
+ title: 'Records Vertical',
3053
+ widgetBody: 'Owns a vertical route surface.',
3054
+ record: {
3055
+ eyebrow: 'Record detail',
3056
+ lede: 'A starter record proving localized SSR, remote hydration, and a vertical-owned Effect BFF can cooperate.',
3057
+ lifecycle: 'Lifecycle',
3058
+ owner: 'Owner',
3059
+ ownerName: 'Workspace Experience',
3060
+ priority: 'Priority',
3061
+ priorityValue: 'P1',
3062
+ ready: 'Ready',
3063
+ sla: 'SLA',
3064
+ slaValue: '24h',
3065
+ state: 'State',
3066
+ status: 'Status',
3067
+ title: 'Starter Record'
3068
+ }
3069
+ },
3070
+ shell: {
3342
3071
  boundaries: {
3343
- checkout: 'en' === language ? 'checkout' : 'pokladna',
3344
- decide: 'en' === language ? 'decide' : 'rozhodování',
3345
- explore: 'en' === language ? 'explore' : 'procházení',
3346
- toggle: 'en' === language ? 'show team boundaries' : 'zobrazit hranice týmů'
3072
+ toggle: 'show vertical boundaries'
3073
+ },
3074
+ hero: {
3075
+ cardOne: 'Add the first business vertical with create <domain> --vertical when the product needs one.',
3076
+ cardOneKicker: 'Verticals',
3077
+ cardTwo: 'Full page markup, styles, and localized content render before hydration.',
3078
+ cardTwoKicker: 'Rendering',
3079
+ empty: 'No MicroVerticals are connected yet.',
3080
+ eyebrow: 'Shell SuperApp starter',
3081
+ lede: 'Start with a production-ready shell. Add MicroVerticals later for real business domains.',
3082
+ primary: 'Shell ready',
3083
+ secondary: 'Add a vertical when needed'
3347
3084
  },
3085
+ language: commonLocaleMessages.en.language,
3086
+ remoteUnavailable: 'Remote vertical unavailable',
3087
+ remotes: {},
3348
3088
  routes: {
3349
- cart: 'en' === language ? 'Cart' : 'Košík',
3350
- home: 'en' === language ? 'Home' : 'Domů',
3351
- listing: 'en' === language ? 'Tractors' : 'Traktory',
3352
- productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru',
3353
- storePicker: 'en' === language ? 'Stores' : 'Prodejci'
3089
+ home: commonLocaleMessages.en.routes.home
3354
3090
  },
3355
- title: 'en' === language ? 'Acre & Iron' : 'Acre & Iron'
3356
- }
3357
- };
3358
- const domain = app.domain ?? app.id;
3359
- const czechLabel = czechLabels[domain] ?? {
3360
- role: domain,
3361
- title: `${app.displayName} CZ`
3362
- };
3363
- return {
3364
- [domain]: {
3365
- language: {
3366
- cs: 'en' === language ? 'Czech' : 'Čeština',
3367
- en: 'en' === language ? 'English' : 'Angličtina',
3368
- switcher: 'en' === language ? 'Language' : 'Jazyk'
3091
+ title: 'UltraModern Workspace'
3092
+ },
3093
+ workspace: {
3094
+ ...commonLocaleMessages.en,
3095
+ federatedSurface: 'Federated surface owned by this vertical.',
3096
+ footer: 'UltraModern workspace',
3097
+ remoteUnavailable: 'Remote vertical unavailable',
3098
+ role: 'workspace',
3099
+ routeSurface: 'Route surface owned by this vertical.',
3100
+ title: 'Workspace Vertical',
3101
+ widgetBody: 'Provides shared UI primitives for the workspace.',
3102
+ directory: {
3103
+ deliveryCopy: 'Owns generated action flows and workflow state.',
3104
+ deliveryName: 'Delivery Operations',
3105
+ deliveryTeam: 'Delivery team',
3106
+ platformCopy: 'Owns shell composition, routing, and shared experience.',
3107
+ platformName: 'Platform Experience',
3108
+ platformTeam: 'Platform team',
3109
+ title: 'Directory'
3369
3110
  },
3370
- role: 'en' === language ? app.domain ?? app.kind : czechLabel.role,
3371
- routes: {
3372
- cart: 'en' === language ? 'Cart' : 'Košík',
3373
- checkout: 'en' === language ? 'Checkout' : 'Pokladna',
3374
- home: 'en' === language ? 'Home' : 'Domů',
3375
- listing: 'en' === language ? 'Tractors' : 'Traktory',
3376
- productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru',
3377
- storePicker: 'en' === language ? 'Store picker' : 'Výběr prodejce',
3378
- thankYou: 'en' === language ? 'Order confirmation' : 'Potvrzení objednávky',
3379
- unavailable: 'en' === language ? 'Unavailable' : 'Nedostupné'
3111
+ header: {
3112
+ brand: 'UltraModern Workspace',
3113
+ directory: 'Directory',
3114
+ navigation: 'Main navigation',
3115
+ workspaces: 'Workspaces'
3380
3116
  },
3381
- title: 'en' === language ? app.displayName : czechLabel.title,
3382
- ...'explore' === domain ? {
3383
- header: {
3384
- machines: 'en' === language ? 'Machines' : 'Stroje',
3385
- navigation: 'en' === language ? 'Main navigation' : 'Hlavní navigace',
3386
- stores: 'en' === language ? 'Stores' : 'Prodejci'
3387
- },
3388
- recommendations: {
3389
- aiFirst: 'en' === language ? 'AI-first option' : 'AI varianta',
3390
- bestRows: 'en' === language ? 'Best for tight rows' : 'Nejlepší do úzkých řádků',
3391
- loaderReady: 'en' === language ? 'Loader-ready' : 'Připraveno pro nakladač',
3392
- title: 'en' === language ? 'Compare alternatives' : 'Porovnat alternativy',
3393
- vineyard: 'en' === language ? 'Vineyard profile' : 'Profil pro vinice'
3394
- },
3395
- stores: {
3396
- northRegion: 'en' === language ? 'North region' : 'Severní region',
3397
- southRegion: 'en' === language ? 'South region' : 'Jižní region',
3398
- title: 'en' === language ? 'Stores' : 'Prodejci'
3399
- }
3400
- } : {},
3401
- ...'decide' === domain ? {
3402
- product: {
3403
- availability: 'en' === language ? 'Availability' : 'Dostupnost',
3404
- eyebrow: 'en' === language ? 'Machine detail' : 'Detail stroje',
3405
- inStock: 'en' === language ? 'In stock' : 'Skladem',
3406
- lede: 'en' === language ? 'A loader-ready tractor for feed, hay, gravel, and winter road work.' : 'Traktor připravený pro nakladač na krmivo, seno, štěrk a zimní údržbu cest.',
3407
- power: 'en' === language ? 'Power' : 'Výkon',
3408
- price: 'en' === language ? 'Price' : 'Cena'
3409
- }
3410
- } : {},
3411
- ...'checkout' === domain ? {
3412
- actions: {
3413
- addToCart: 'en' === language ? 'Add to cart' : 'Přidat do košíku',
3414
- remove: 'en' === language ? 'Remove' : 'Odebrat',
3415
- viewCart: 'en' === language ? 'View cart' : 'Zobrazit košík'
3416
- },
3417
- cart: {
3418
- empty: 'en' === language ? 'Your cart is empty.' : 'Košík je prázdný.',
3419
- title: 'en' === language ? 'Your cart' : 'Váš košík',
3420
- total: 'en' === language ? 'Total' : 'Celkem'
3421
- }
3422
- } : {}
3117
+ highlights: {
3118
+ actions: 'Action lane',
3119
+ actionsTitle: 'Trigger a cross-vertical action',
3120
+ records: 'Record lane',
3121
+ recordsTitle: 'Open a route-owned record',
3122
+ shell: 'Shell lane',
3123
+ shellTitle: 'Compose verticals in the shell',
3124
+ title: 'Generated vertical surfaces'
3125
+ }
3423
3126
  }
3127
+ }
3128
+ };
3129
+ const createFallbackLocaleMessages = (app, language)=>({
3130
+ ...commonLocaleMessages[language],
3131
+ federatedSurface: generatedLocaleResources[language].workspace.federatedSurface,
3132
+ remoteUnavailable: generatedLocaleResources[language].workspace.remoteUnavailable,
3133
+ role: app.domain ?? app.kind,
3134
+ routeSurface: generatedLocaleResources[language].workspace.routeSurface,
3135
+ title: app.displayName,
3136
+ widgetBody: 'vertical' === app.kind ? generatedLocaleResources[language].records.widgetBody : generatedLocaleResources[language].workspace.widgetBody
3137
+ });
3138
+ function createAppLocaleMessages(app, language) {
3139
+ const domain = app.domain ?? app.id;
3140
+ const messageKey = 'shell' === app.kind ? 'shell' : domain;
3141
+ const messages = generatedLocaleResources[language][messageKey] ?? createFallbackLocaleMessages(app, language);
3142
+ return {
3143
+ [messageKey]: messages
3424
3144
  };
3425
3145
  }
3426
- function createDesignButton(app) {
3427
- const tw = createTw(tailwindPrefixForApp(app));
3428
- return `export default function Button({ label }: { label: string }) {
3429
- return (
3430
- <button className="${tw('rounded-full text-um-foreground')}" type="button">
3431
- {label}
3432
- </button>
3433
- );
3434
- }
3435
- `;
3436
- }
3437
- function createDesignTokens() {
3438
- return `export const designTokens = {
3439
- color: {
3440
- accent: '#2f8f68',
3441
- foreground: '#133225',
3442
- },
3443
- radius: {
3444
- control: '999px',
3445
- },
3446
- } as const;
3447
- `;
3146
+ function createAppPublicLocaleMessages(app, language, remotes = []) {
3147
+ if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
3148
+ return Object.assign({}, createAppLocaleMessages(app, language), ...remotes.map((remote)=>createAppLocaleMessages(remote, language)));
3448
3149
  }
3449
- function createCheckoutCartStore() {
3150
+ function createActionQueueStore() {
3450
3151
  return `import { useEffect, useMemo, useState } from 'react';
3451
3152
 
3452
- export type CartLine = {
3153
+ export type ActionLine = {
3453
3154
  id: string;
3454
- name: string;
3455
- price: number;
3456
- quantity: number;
3155
+ nameKey: string;
3156
+ status: 'queued' | 'complete';
3457
3157
  };
3458
3158
 
3459
- const storageKey = 'ultramodern-tractor-cart';
3460
- const cartEvent = 'ultramodern-cart-change';
3461
- const fieldLoader: CartLine = {
3462
- id: 'field-loader-112',
3463
- name: 'Field Loader 112',
3464
- price: 42500,
3465
- quantity: 1,
3159
+ const storageKey = 'ultramodern-action-queue';
3160
+ const queueEvent = 'ultramodern-action-queue-change';
3161
+ const starterAction: ActionLine = {
3162
+ id: 'starter-action',
3163
+ nameKey: 'actions.queue.starterAction',
3164
+ status: 'queued',
3466
3165
  };
3467
3166
 
3468
- const readCart = (): CartLine[] => {
3167
+ const readQueue = (): ActionLine[] => {
3469
3168
  if (typeof window === 'undefined') {
3470
3169
  return [];
3471
3170
  }
3472
3171
 
3473
3172
  try {
3474
3173
  const value = window.localStorage.getItem(storageKey);
3475
- return value ? (JSON.parse(value) as CartLine[]) : [];
3174
+ return value ? (JSON.parse(value) as ActionLine[]) : [];
3476
3175
  } catch {
3477
3176
  return [];
3478
3177
  }
3479
3178
  };
3480
3179
 
3481
- const writeCart = (lines: CartLine[]) => {
3180
+ const writeQueue = (lines: ActionLine[]) => {
3482
3181
  if (typeof window === 'undefined') {
3483
3182
  return;
3484
3183
  }
3485
3184
 
3486
3185
  window.localStorage.setItem(storageKey, JSON.stringify(lines));
3487
- window.dispatchEvent(new CustomEvent(cartEvent));
3186
+ window.dispatchEvent(new CustomEvent(queueEvent));
3488
3187
  };
3489
3188
 
3490
3189
  const updateLine = (
3491
3190
  id: string,
3492
- updater: (line: CartLine) => CartLine | undefined,
3191
+ updater: (line: ActionLine) => ActionLine | undefined,
3493
3192
  ) => {
3494
- const next = readCart()
3193
+ const next = readQueue()
3495
3194
  .map(line => (line.id === id ? updater(line) : line))
3496
- .filter((line): line is CartLine => Boolean(line));
3497
- writeCart(next);
3195
+ .filter((line): line is ActionLine => Boolean(line));
3196
+ writeQueue(next);
3498
3197
  };
3499
3198
 
3500
- export function useCartLines() {
3501
- const [lines, setLines] = useState<CartLine[]>(() => readCart());
3199
+ export function useActionQueue() {
3200
+ const [lines, setLines] = useState<ActionLine[]>(() => readQueue());
3502
3201
 
3503
3202
  useEffect(() => {
3504
- const refresh = () => setLines(readCart());
3505
- window.addEventListener(cartEvent, refresh);
3203
+ const refresh = () => setLines(readQueue());
3204
+ window.addEventListener(queueEvent, refresh);
3506
3205
  window.addEventListener('storage', refresh);
3507
3206
  refresh();
3508
3207
 
3509
3208
  return () => {
3510
- window.removeEventListener(cartEvent, refresh);
3209
+ window.removeEventListener(queueEvent, refresh);
3511
3210
  window.removeEventListener('storage', refresh);
3512
3211
  };
3513
3212
  }, []);
@@ -3515,27 +3214,22 @@ export function useCartLines() {
3515
3214
  return useMemo(
3516
3215
  () => ({
3517
3216
  lines,
3518
- total: lines.reduce((sum, line) => sum + line.price * line.quantity, 0),
3519
- addFieldLoader: () => {
3520
- const existing = readCart();
3521
- const match = existing.find(line => line.id === fieldLoader.id);
3522
- writeCart(
3217
+ addStarterAction: () => {
3218
+ const existing = readQueue();
3219
+ const match = existing.find(line => line.id === starterAction.id);
3220
+ writeQueue(
3523
3221
  match
3524
3222
  ? existing.map(line =>
3525
- line.id === fieldLoader.id
3526
- ? { ...line, quantity: line.quantity + 1 }
3223
+ line.id === starterAction.id
3224
+ ? { ...line, status: 'queued' as const }
3527
3225
  : line,
3528
3226
  )
3529
- : [...existing, fieldLoader],
3227
+ : [...existing, starterAction],
3530
3228
  );
3531
3229
  },
3532
- increment: (id: string) =>
3533
- updateLine(id, line => ({ ...line, quantity: line.quantity + 1 })),
3534
- decrement: (id: string) =>
3535
- updateLine(id, line =>
3536
- line.quantity > 1 ? { ...line, quantity: line.quantity - 1 } : undefined,
3537
- ),
3538
- remove: (id: string) => writeCart(readCart().filter(line => line.id !== id)),
3230
+ complete: (id: string) =>
3231
+ updateLine(id, line => ({ ...line, status: 'complete' as const })),
3232
+ remove: (id: string) => writeQueue(readQueue().filter(line => line.id !== id)),
3539
3233
  }),
3540
3234
  [lines],
3541
3235
  );
@@ -3552,36 +3246,35 @@ function createSharedDesignTokensCss() {
3552
3246
  }
3553
3247
  `;
3554
3248
  }
3555
- function serviceEffectApiExport(service) {
3249
+ function verticalEffectApiExport(service) {
3556
3250
  return `${toCamelCase(effectApiStem(service))}EffectApi`;
3557
3251
  }
3558
- function serviceEffectGroupName(service) {
3252
+ function verticalEffectGroupName(service) {
3559
3253
  return toCamelCase(effectApiStem(service));
3560
3254
  }
3561
- function serviceEffectApiName(service) {
3255
+ function verticalEffectApiName(service) {
3562
3256
  return `${toPascalCase(effectApiStem(service))}EffectApi`;
3563
3257
  }
3564
- function serviceEffectSchemaExport(service) {
3258
+ function verticalEffectSchemaExport(service) {
3565
3259
  return `${toCamelCase(effectApiStem(service))}ItemSchema`;
3566
3260
  }
3567
- function serviceEffectMarkerSchemaExport(service) {
3261
+ function verticalEffectMarkerSchemaExport(service) {
3568
3262
  return `${toCamelCase(effectApiStem(service))}MarkerSchema`;
3569
3263
  }
3570
- function serviceEffectReadinessSchemaExport(service) {
3264
+ function verticalEffectReadinessSchemaExport(service) {
3571
3265
  return `${toCamelCase(effectApiStem(service))}ReadinessSchema`;
3572
3266
  }
3573
- function serviceEffectErrorStem(service) {
3574
- const stem = effectApiStem(service);
3575
- return 'recommendations' === stem ? 'recommendation' : stem;
3267
+ function verticalEffectErrorStem(service) {
3268
+ return effectApiStem(service);
3576
3269
  }
3577
- function serviceEffectCreatePayloadSchemaExport(service) {
3270
+ function verticalEffectCreatePayloadSchemaExport(service) {
3578
3271
  return `${toCamelCase(effectApiStem(service))}CreatePayloadSchema`;
3579
3272
  }
3580
- function serviceEffectNotFoundErrorExport(service) {
3581
- return `${toPascalCase(serviceEffectErrorStem(service))}NotFound`;
3273
+ function verticalEffectNotFoundErrorExport(service) {
3274
+ return `${toPascalCase(verticalEffectErrorStem(service))}NotFound`;
3582
3275
  }
3583
- function serviceEffectNotFoundSchemaExport(service) {
3584
- return `${toCamelCase(serviceEffectErrorStem(service))}NotFoundSchema`;
3276
+ function verticalEffectNotFoundSchemaExport(service) {
3277
+ return `${toCamelCase(verticalEffectErrorStem(service))}NotFoundSchema`;
3585
3278
  }
3586
3279
  function createEffectSharedApiImports() {
3587
3280
  return `import {
@@ -3594,17 +3287,17 @@ function createEffectSharedApiImports() {
3594
3287
  `;
3595
3288
  }
3596
3289
  function createEffectSharedApiContract(service) {
3597
- const schemaExport = serviceEffectSchemaExport(service);
3598
- const markerSchemaExport = serviceEffectMarkerSchemaExport(service);
3599
- const readinessSchemaExport = serviceEffectReadinessSchemaExport(service);
3600
- const createPayloadSchemaExport = serviceEffectCreatePayloadSchemaExport(service);
3601
- const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
3602
- const notFoundSchemaExport = serviceEffectNotFoundSchemaExport(service);
3603
- const apiExport = serviceEffectApiExport(service);
3604
- const apiName = serviceEffectApiName(service);
3605
- const groupName = serviceEffectGroupName(service);
3290
+ const schemaExport = verticalEffectSchemaExport(service);
3291
+ const markerSchemaExport = verticalEffectMarkerSchemaExport(service);
3292
+ const readinessSchemaExport = verticalEffectReadinessSchemaExport(service);
3293
+ const createPayloadSchemaExport = verticalEffectCreatePayloadSchemaExport(service);
3294
+ const notFoundErrorExport = verticalEffectNotFoundErrorExport(service);
3295
+ const notFoundSchemaExport = verticalEffectNotFoundSchemaExport(service);
3296
+ const apiExport = verticalEffectApiExport(service);
3297
+ const apiName = verticalEffectApiName(service);
3298
+ const groupName = verticalEffectGroupName(service);
3606
3299
  const stem = effectApiStem(service);
3607
- const servicePrefix = effectApiPrefix(service);
3300
+ const apiPrefix = effectApiPrefix(service);
3608
3301
  return `export const ${markerSchemaExport} = Schema.Struct({
3609
3302
  appId: Schema.String,
3610
3303
  packageName: Schema.String,
@@ -3719,10 +3412,10 @@ export const ${groupName}OperationContexts = {
3719
3412
  } satisfies Record<string, OperationContext>;
3720
3413
 
3721
3414
  export const ${groupName}ApiContract = {
3722
- basePath: '${servicePrefix}/effect/${stem}',
3415
+ apiPrefix: '${apiPrefix}',
3416
+ basePath: '${apiPrefix}/effect/${stem}',
3723
3417
  ownerId: '${service.id}',
3724
- servicePrefix: '${servicePrefix}',
3725
- readinessPath: '${servicePrefix}/effect/${stem}/readiness',
3418
+ readinessPath: '${apiPrefix}/effect/${stem}/readiness',
3726
3419
  } as const;
3727
3420
  `;
3728
3421
  }
@@ -3730,14 +3423,14 @@ function createEffectSharedApi(service) {
3730
3423
  if (service) return `${createEffectSharedApiImports()}
3731
3424
  ${createEffectSharedApiContract(service)}`;
3732
3425
  return `export const sharedEffectApiPackage = {
3733
- scope: 'external-effect-service-contracts',
3426
+ scope: 'external-effect-api-contracts',
3734
3427
  } as const;
3735
3428
  `;
3736
3429
  }
3737
3430
  function createEffectServiceEntry(scope, service, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
3738
- const apiExport = serviceEffectApiExport(service);
3739
- const groupName = serviceEffectGroupName(service);
3740
- const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
3431
+ const apiExport = verticalEffectApiExport(service);
3432
+ const groupName = verticalEffectGroupName(service);
3433
+ const notFoundErrorExport = verticalEffectNotFoundErrorExport(service);
3741
3434
  const stem = effectApiStem(service);
3742
3435
  return `import {
3743
3436
  defineEffectBff,
@@ -3850,11 +3543,11 @@ export default defineEffectBff({
3850
3543
  `;
3851
3544
  }
3852
3545
  function createEffectClient(service, contractImportPath) {
3853
- const apiExport = serviceEffectApiExport(service);
3854
- const contractExport = serviceEffectGroupName(service);
3546
+ const apiExport = verticalEffectApiExport(service);
3547
+ const contractExport = verticalEffectGroupName(service);
3855
3548
  const stem = effectApiStem(service);
3856
- const groupName = serviceEffectGroupName(service);
3857
- const singular = serviceEffectErrorStem(service);
3549
+ const groupName = verticalEffectGroupName(service);
3550
+ const singular = verticalEffectErrorStem(service);
3858
3551
  const clientOptionsName = `${toPascalCase(stem)}ClientOptions`;
3859
3552
  const createClientName = `create${toPascalCase(stem)}Client`;
3860
3553
  const listName = `list${toPascalCase(stem)}`;
@@ -3883,7 +3576,7 @@ export function ${createClientName}(
3883
3576
  options: ${clientOptionsName} = {},
3884
3577
  ) {
3885
3578
  return makeEffectHttpApiClient(${apiExport}, {
3886
- baseUrl: options.baseUrl ?? ${contractExport}ApiContract.servicePrefix,
3579
+ baseUrl: options.baseUrl ?? ${contractExport}ApiContract.apiPrefix,
3887
3580
  });
3888
3581
  }
3889
3582
 
@@ -3950,33 +3643,21 @@ export function ${createName}(
3950
3643
  }
3951
3644
  `;
3952
3645
  }
3953
- function createShellEffectClient(scope) {
3954
- return `export {
3955
- createCheckout,
3956
- createCheckoutClient,
3957
- getCheckout,
3958
- getCheckoutReadiness,
3959
- listCheckout,
3960
- type CheckoutClientOptions,
3961
- } from '${ultramodern_workspace_packageName(scope, 'checkout')}/effect/client';
3962
-
3963
- export {
3964
- createDecide,
3965
- createDecideClient,
3966
- getDecide,
3967
- getDecideReadiness,
3968
- listDecide,
3969
- type DecideClientOptions,
3970
- } from '${ultramodern_workspace_packageName(scope, 'decide')}/effect/client';
3971
-
3972
- export {
3973
- createExplore,
3974
- createExploreClient,
3975
- getExplore,
3976
- getExploreReadiness,
3977
- listExplore,
3978
- type ExploreClientOptions,
3979
- } from '${ultramodern_workspace_packageName(scope, 'explore')}/effect/client';
3646
+ function createShellEffectClient(scope, remotes = []) {
3647
+ const exports = verticalEffectApps(remotes).map((remote)=>{
3648
+ const stem = effectApiStem(remote);
3649
+ const pascalStem = toPascalCase(stem);
3650
+ const pascalSingular = toPascalCase(verticalEffectErrorStem(remote));
3651
+ return `export {
3652
+ create${pascalSingular},
3653
+ create${pascalStem}Client,
3654
+ get${pascalSingular},
3655
+ get${pascalStem}Readiness,
3656
+ list${pascalStem},
3657
+ type ${pascalStem}ClientOptions,
3658
+ } from '${ultramodern_workspace_packageName(scope, remote.packageSuffix)}/effect/client';`;
3659
+ }).join('\n\n');
3660
+ return exports ? `${exports}\n` : `export const ultramodernVerticalClients = [] as const;
3980
3661
  `;
3981
3662
  }
3982
3663
  function toPascalCase(value) {
@@ -4015,71 +3696,71 @@ function createEffectRequestContextContract() {
4015
3696
  }
4016
3697
  function createEffectDomainOperations(app) {
4017
3698
  const stem = effectApiStem(app);
4018
- const group = serviceEffectGroupName(app);
3699
+ const group = verticalEffectGroupName(app);
4019
3700
  const basePath = `/effect/${stem}`;
4020
- if ('checkout' === stem) return {
4021
- cartSnapshot: {
4022
- client: 'listCheckout',
3701
+ if ('actions' === stem) return {
3702
+ actionQueue: {
3703
+ client: 'listActions',
4023
3704
  method: 'GET',
4024
3705
  path: basePath,
4025
- resource: 'cart',
3706
+ resource: 'action-queue',
4026
3707
  owner: app.id
4027
3708
  },
4028
- cartMutation: {
4029
- client: 'createCheckout',
3709
+ actionMutation: {
3710
+ client: 'createActions',
4030
3711
  method: 'POST',
4031
3712
  path: basePath,
4032
- resource: 'cart-line',
3713
+ resource: 'action',
4033
3714
  owner: app.id
4034
3715
  },
4035
- orderConfirmation: {
4036
- client: 'getCheckout',
3716
+ actionStatus: {
3717
+ client: 'getActions',
4037
3718
  method: 'GET',
4038
3719
  path: `${basePath}/:id`,
4039
- resource: 'order',
3720
+ resource: 'action-status',
4040
3721
  owner: app.id
4041
3722
  }
4042
3723
  };
4043
- if ('decide' === stem) return {
4044
- productDetail: {
4045
- client: 'getDecide',
3724
+ if ('records' === stem) return {
3725
+ recordDetail: {
3726
+ client: 'getRecords',
4046
3727
  method: 'GET',
4047
3728
  path: `${basePath}/:id`,
4048
- resource: 'product-detail',
3729
+ resource: 'record',
4049
3730
  owner: app.id
4050
3731
  },
4051
- configurationDraft: {
4052
- client: 'createDecide',
3732
+ recordDraft: {
3733
+ client: 'createRecords',
4053
3734
  method: 'POST',
4054
3735
  path: basePath,
4055
- resource: 'configuration',
3736
+ resource: 'record-draft',
4056
3737
  owner: app.id
4057
3738
  },
4058
- productList: {
4059
- client: 'listDecide',
3739
+ recordList: {
3740
+ client: 'listRecords',
4060
3741
  method: 'GET',
4061
3742
  path: basePath,
4062
- resource: 'products',
3743
+ resource: 'records',
4063
3744
  owner: app.id
4064
3745
  }
4065
3746
  };
4066
3747
  return {
4067
- recommendationFeed: {
3748
+ workspaceFeed: {
4068
3749
  client: `list${toPascalCase(stem)}`,
4069
3750
  method: 'GET',
4070
3751
  path: basePath,
4071
- resource: 'recommendations',
3752
+ resource: 'workspace-items',
4072
3753
  owner: app.id
4073
3754
  },
4074
- recommendationDetail: {
4075
- client: `get${toPascalCase(serviceEffectErrorStem(app))}`,
3755
+ workspaceDetail: {
3756
+ client: `get${toPascalCase(verticalEffectErrorStem(app))}`,
4076
3757
  method: 'GET',
4077
3758
  path: `${basePath}/:id`,
4078
- resource: 'recommendation',
3759
+ resource: 'workspace-item',
4079
3760
  owner: app.id
4080
3761
  },
4081
- recommendationCreate: {
4082
- client: `create${toPascalCase(serviceEffectErrorStem(app))}`,
3762
+ workspaceCreate: {
3763
+ client: `create${toPascalCase(verticalEffectErrorStem(app))}`,
4083
3764
  method: 'POST',
4084
3765
  path: basePath,
4085
3766
  resource: group,
@@ -4113,28 +3794,29 @@ function effectApiTopologyMetadata(app) {
4113
3794
  }
4114
3795
  };
4115
3796
  }
4116
- function createTopology(scope) {
3797
+ function createTopology(scope, remotes = []) {
3798
+ const shellHost = createShellHost(remotes);
4117
3799
  return {
4118
3800
  schemaVersion: 1,
4119
3801
  id: 'ultramodern-superapp-workspace-reference-topology',
4120
- description: 'Generated UltraModern workspace skeleton with full-stack vertical ownership.',
3802
+ description: 'Generated UltraModern SuperApp shell that can grow by adding full-stack verticals.',
4121
3803
  preset: 'presetUltramodern',
4122
3804
  shell: {
4123
3805
  id: shellApp.id,
4124
3806
  kind: 'shell',
4125
3807
  package: ultramodern_workspace_packageName(scope, shellApp.packageSuffix),
4126
- verticalRefs: shellApp.verticalRefs,
3808
+ verticalRefs: shellHost.verticalRefs,
4127
3809
  moduleFederation: {
4128
3810
  role: 'host',
4129
3811
  name: shellApp.mfName,
4130
- remotes: createModuleFederationRemoteContracts(shellApp),
3812
+ remotes: createModuleFederationRemoteContracts(shellHost, remotes),
4131
3813
  ssr: true,
4132
3814
  sharedContractVersion: 'mf-ssr-contract-v1'
4133
3815
  },
4134
3816
  cloudflare: createCloudflareDeployContract(scope, shellApp),
4135
3817
  ownership: shellApp.ownership
4136
3818
  },
4137
- verticals: verticalApps.map((vertical)=>({
3819
+ verticals: remotes.map((vertical)=>({
4138
3820
  id: vertical.id,
4139
3821
  kind: vertical.kind,
4140
3822
  domain: vertical.domain,
@@ -4173,13 +3855,13 @@ function createTopology(scope) {
4173
3855
  }
4174
3856
  };
4175
3857
  }
4176
- function createOwnership(scope) {
3858
+ function createOwnership(scope, remotes = []) {
4177
3859
  return {
4178
3860
  schemaVersion: 1,
4179
3861
  preset: 'presetUltramodern',
4180
3862
  owners: [
4181
3863
  shellApp,
4182
- ...verticalApps,
3864
+ ...remotes,
4183
3865
  ...sharedPackages.map((sharedPackage)=>({
4184
3866
  id: sharedPackage.id,
4185
3867
  packageSuffix: sharedPackage.id,
@@ -4206,23 +3888,23 @@ function createOwnership(scope) {
4206
3888
  }))
4207
3889
  };
4208
3890
  }
4209
- function createDevelopmentOverlay() {
3891
+ function createDevelopmentOverlay(remotes = []) {
4210
3892
  return {
4211
3893
  schemaVersion: 1,
4212
3894
  environment: 'development',
4213
3895
  preset: 'presetUltramodern',
4214
3896
  ports: Object.fromEntries([
4215
3897
  shellApp,
4216
- ...verticalApps
3898
+ ...remotes
4217
3899
  ].map((app)=>[
4218
3900
  app.id,
4219
3901
  app.port
4220
3902
  ])),
4221
- manifests: Object.fromEntries(verticalApps.map((remote)=>[
3903
+ manifests: Object.fromEntries(remotes.map((remote)=>[
4222
3904
  remote.id,
4223
3905
  `http://localhost:${remote.port}/mf-manifest.json`
4224
3906
  ])),
4225
- apis: Object.fromEntries(verticalEffectApps().map((app)=>[
3907
+ apis: Object.fromEntries(verticalEffectApps(remotes).map((app)=>[
4226
3908
  app.id,
4227
3909
  `http://localhost:${app.port}${effectApiPrefix(app)}`
4228
3910
  ]))
@@ -4258,8 +3940,8 @@ function createPackageSourceMetadata(scope, packageSource) {
4258
3940
  function createEffectOperationContract(target) {
4259
3941
  const stem = effectApiStem(target);
4260
3942
  return {
4261
- group: serviceEffectGroupName(target),
4262
- notFound: serviceEffectNotFoundErrorExport(target),
3943
+ group: verticalEffectGroupName(target),
3944
+ notFound: verticalEffectNotFoundErrorExport(target),
4263
3945
  operations: {
4264
3946
  list: {
4265
3947
  method: 'GET',
@@ -4593,8 +4275,7 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
4593
4275
  };
4594
4276
  }
4595
4277
  function createGeneratedContract(scope, apps = [
4596
- shellApp,
4597
- ...verticalApps
4278
+ createShellHost()
4598
4279
  ], enableTailwind = true) {
4599
4280
  return {
4600
4281
  schemaVersion: 1,
@@ -4626,7 +4307,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4626
4307
  id: 'modernjs-ultramodern-superapp-workspace',
4627
4308
  version: modernVersion,
4628
4309
  displayName: 'Modern.js UltraModern SuperApp Workspace',
4629
- description: 'Canonical shell, full-stack verticals, shared packages, and topology skeleton.',
4310
+ description: 'Growable SuperApp shell, shared packages, and topology skeleton.',
4630
4311
  compatibilityLane: 'ultramodern-mv',
4631
4312
  minimumModernVersion: modernVersion
4632
4313
  },
@@ -4669,7 +4350,6 @@ function createTemplateManifest(modernVersion, packageSource) {
4669
4350
  'oxlint.config.ts',
4670
4351
  'pnpm-workspace.yaml',
4671
4352
  "scripts/**",
4672
- 'services/**',
4673
4353
  'topology/**',
4674
4354
  'tsconfig.base.json'
4675
4355
  ],
@@ -4740,7 +4420,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4740
4420
  }
4741
4421
  };
4742
4422
  }
4743
- function createAssertMfTypesScript(remotes = verticalApps) {
4423
+ function createAssertMfTypesScript(remotes = []) {
4744
4424
  return `import fs from 'node:fs';
4745
4425
  import path from 'node:path';
4746
4426
 
@@ -4812,12 +4492,12 @@ for (const appDir of appDirs) {
4812
4492
  }
4813
4493
  `;
4814
4494
  }
4815
- function createWorkspaceValidationScript(scope, enableTailwind, remotes = verticalApps) {
4495
+ function createWorkspaceValidationScript(scope, enableTailwind, remotes = []) {
4816
4496
  const verticals = remotes.filter(appHasEffectApi).map((remote)=>({
4817
4497
  id: remote.id,
4818
4498
  domain: remote.domain,
4819
4499
  stem: remote.effectApi.stem,
4820
- group: serviceEffectGroupName(remote),
4500
+ group: verticalEffectGroupName(remote),
4821
4501
  path: remote.directory,
4822
4502
  mfName: remote.mfName,
4823
4503
  apiPrefix: remote.effectApi.prefix,
@@ -4834,6 +4514,9 @@ function createWorkspaceValidationScript(scope, enableTailwind, remotes = vertic
4834
4514
  const oldRemotePaths = [
4835
4515
  'apps/remotes'
4836
4516
  ];
4517
+ const expectedBuildScript = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types' : 'pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types';
4518
+ const expectedCloudflareBuildScript = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types' : 'pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types';
4519
+ const expectedCloudflareDeployScript = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy' : 'pnpm --filter "./apps/shell-super-app" run cloudflare:deploy';
4837
4520
  return `import { execFileSync } from 'node:child_process';
4838
4521
  import fs from 'node:fs';
4839
4522
  import path from 'node:path';
@@ -4845,6 +4528,9 @@ const tailwindEnabled = ${JSON.stringify(enableTailwind)};
4845
4528
  const fullStackVerticals = ${JSON.stringify(verticals, null, 2)};
4846
4529
  const shellNamespace = ${JSON.stringify(shellNamespace)};
4847
4530
  const oldRemotePaths = ${JSON.stringify(oldRemotePaths, null, 2)};
4531
+ const expectedBuildScript = ${JSON.stringify(expectedBuildScript)};
4532
+ const expectedCloudflareBuildScript = ${JSON.stringify(expectedCloudflareBuildScript)};
4533
+ const expectedCloudflareDeployScript = ${JSON.stringify(expectedCloudflareDeployScript)};
4848
4534
 
4849
4535
  const readText = relativePath => fs.readFileSync(path.join(root, relativePath), 'utf-8');
4850
4536
  const readJson = relativePath => JSON.parse(readText(relativePath));
@@ -4900,7 +4586,7 @@ const requiredPaths = [
4900
4586
  'apps/shell-super-app/module-federation.config.ts',
4901
4587
  'apps/shell-super-app/src/modern-app-env.d.ts',
4902
4588
  'apps/shell-super-app/src/modern.runtime.ts',
4903
- 'apps/shell-super-app/src/effect/recommendations-client.ts',
4589
+ 'apps/shell-super-app/src/effect/vertical-clients.ts',
4904
4590
  'apps/shell-super-app/locales/en/translation.json',
4905
4591
  \`apps/shell-super-app/locales/en/\${shellNamespace}.json\`,
4906
4592
  'apps/shell-super-app/locales/cs/translation.json',
@@ -4956,8 +4642,6 @@ for (const requiredPath of requiredPaths) {
4956
4642
  for (const oldRemotePath of oldRemotePaths) {
4957
4643
  assertNotExists(oldRemotePath);
4958
4644
  }
4959
- assertNotExists('services/service-recommendations-effect');
4960
-
4961
4645
  const rootPackage = readJson('package.json');
4962
4646
  const packageSource = readJson('.modernjs/ultramodern-package-source.json');
4963
4647
  const generatedContract = readJson('.modernjs/ultramodern-generated-contract.json');
@@ -4973,13 +4657,13 @@ assert(rootPackage.modernjs?.packageSource?.strategy === packageSource.strategy,
4973
4657
  assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'install', 'Package source strategy must be workspace or install');
4974
4658
  assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
4975
4659
  assert(
4976
- rootPackage.scripts?.build ===
4977
- 'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
4660
+ rootPackage.scripts?.build === expectedBuildScript,
4978
4661
  'Root build script must build verticals before shell',
4979
4662
  );
4663
+ assert(rootPackage.scripts?.['cloudflare:build'] === expectedCloudflareBuildScript, 'Root cloudflare:build script is incorrect');
4980
4664
  assert(rootPackage.scripts?.['ultramodern:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose ultramodern:check');
4981
4665
  assert(rootPackage.scripts?.['ultramodern:assert-mf-types'] === 'node ./scripts/assert-mf-types.mjs', 'Root must expose ultramodern:assert-mf-types');
4982
- assert(rootPackage.scripts?.['cloudflare:deploy']?.includes('run cloudflare:deploy'), 'Root must expose cloudflare:deploy');
4666
+ assert(rootPackage.scripts?.['cloudflare:deploy'] === expectedCloudflareDeployScript, 'Root must expose cloudflare:deploy');
4983
4667
  assert(rootPackage.scripts?.['cloudflare:proof'] === 'node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json', 'Root must expose cloudflare:proof');
4984
4668
  assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
4985
4669
  assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
@@ -4988,7 +4672,7 @@ assert(rootPackage.scripts?.postinstall === 'node ./scripts/bootstrap-agent-skil
4988
4672
  const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
4989
4673
  assert(
4990
4674
  JSON.stringify(generatedContract.apps?.map(app => app.id)) === JSON.stringify(expectedAppIds),
4991
- 'Generated contract must contain shell plus the Tractor full-stack verticals',
4675
+ 'Generated contract must contain shell plus the full-stack verticals',
4992
4676
  );
4993
4677
  assert(generatedContract.cssFederation?.sharedDesignTokens?.owner?.id === 'shared-design-tokens', 'CSS federation must declare shared design token ownership');
4994
4678
  assert(generatedContract.cssFederation?.sharedDesignTokens?.role === 'shared-design-tokens', 'CSS federation must mark shared-design-tokens as token owner');
@@ -5010,7 +4694,7 @@ const expectedZephyrDependencies = Object.fromEntries(
5010
4694
  assert(
5011
4695
  JSON.stringify(shellPackage['zephyr:dependencies']) ===
5012
4696
  JSON.stringify(expectedZephyrDependencies),
5013
- 'Shell Zephyr dependencies must reference every Tractor vertical package',
4697
+ 'Shell Zephyr dependencies must reference every vertical package',
5014
4698
  );
5015
4699
  const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
5016
4700
  assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
@@ -5028,16 +4712,16 @@ assert(shellContract?.styling?.federation?.dedupe?.duplicateBaseStylesAllowed ==
5028
4712
  assert(shellContract?.styling?.federation?.ssr?.firstPaintRequired === true, 'Shell CSS must be required for SSR first paint');
5029
4713
  assert(
5030
4714
  topology.shell?.verticalRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
5031
- 'Topology shell verticalRefs must match Tractor verticals',
4715
+ 'Topology shell verticalRefs must match generated verticals',
5032
4716
  );
5033
- assert(topology.verticals?.length === fullStackVerticals.length, 'Topology must contain only Tractor verticals');
4717
+ assert(topology.verticals?.length === fullStackVerticals.length, 'Topology must contain only generated verticals');
5034
4718
  assert(!('remotes' in topology), 'Topology must not expose legacy remotes; use verticals');
5035
4719
  assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, not effectServices');
5036
4720
 
5037
4721
  for (const vertical of fullStackVerticals) {
5038
4722
  const packageJson = readJson(\`\${vertical.path}/package.json\`);
5039
4723
  assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
5040
- assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json', \`\${vertical.id} must expose cloudflare:deploy\`);
4724
+ assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy', \`\${vertical.id} must expose cloudflare:deploy\`);
5041
4725
  assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
5042
4726
  assert(packageJson.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
5043
4727
  assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
@@ -5169,10 +4853,10 @@ function parseArgs(argv) {
5169
4853
 
5170
4854
  function printHelp() {
5171
4855
  process.stdout.write(\`Usage:
5172
- node scripts/proof-cloudflare-version.mjs [--app explore] [--out evidence.json] [--require-public-urls]
4856
+ node scripts/proof-cloudflare-version.mjs [--app workspace] [--out evidence.json] [--require-public-urls]
5173
4857
 
5174
4858
  Set each app's public URL using the contract env key, for example:
5175
- ULTRAMODERN_PUBLIC_URL_EXPLORE=https://explore.example.workers.dev
4859
+ ULTRAMODERN_PUBLIC_URL_WORKSPACE=https://workspace.example.workers.dev
5176
4860
  \`);
5177
4861
  }
5178
4862
 
@@ -5437,64 +5121,56 @@ main().then(
5437
5121
  );
5438
5122
  `;
5439
5123
  }
5440
- function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = verticalApps) {
5441
- writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript());
5124
+ function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = []) {
5125
+ writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript(remotes));
5442
5126
  writeFileReplacing(targetDir, "scripts/validate-ultramodern-workspace.mjs", createWorkspaceValidationScript(scope, enableTailwind, remotes));
5443
5127
  writeFileReplacing(targetDir, "scripts/proof-cloudflare-version.mjs", createCloudflareVersionProofScript());
5444
5128
  }
5445
- function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
5129
+ function writeApp(targetDir, scope, app, packageSource, enableTailwind, remotes = []) {
5130
+ const resolvedApp = 'shell' === app.kind ? createShellHost(remotes) : app;
5446
5131
  const writeAppFile = (relativePath, content)=>{
5447
- writeFile(targetDir, `${app.directory}/${relativePath}`, content);
5132
+ writeFile(targetDir, `${resolvedApp.directory}/${relativePath}`, content);
5448
5133
  };
5449
- writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
5450
- writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
5451
- writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, createAppEnvDts(app));
5452
- writeFile(targetDir, `${app.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, app));
5453
- writeFile(targetDir, `${app.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(app));
5454
- writeFile(targetDir, `${app.directory}/modern.config.ts`, createAppModernConfig(scope, app));
5455
- writeFile(targetDir, `${app.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(app));
5456
- writeJson(targetDir, `${app.directory}/locales/en/translation.json`, createAppLocaleMessages(app, 'en'));
5457
- writeJson(targetDir, `${app.directory}/locales/en/${appI18nNamespace(app)}.json`, createAppLocaleMessages(app, 'en'));
5458
- writeJson(targetDir, `${app.directory}/locales/cs/translation.json`, createAppLocaleMessages(app, 'cs'));
5459
- writeJson(targetDir, `${app.directory}/locales/cs/${appI18nNamespace(app)}.json`, createAppLocaleMessages(app, 'cs'));
5460
- writeFile(targetDir, `${app.directory}/src/routes/index.css`, createAppStyles(enableTailwind, scope, app));
5134
+ writeJson(targetDir, `${resolvedApp.directory}/package.json`, createAppPackage(scope, resolvedApp, packageSource, enableTailwind, remotes));
5135
+ writeJson(targetDir, `${resolvedApp.directory}/tsconfig.json`, createPackageTsConfig(resolvedApp.directory, appHasEffectApi(resolvedApp)));
5136
+ writeFile(targetDir, `${resolvedApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(resolvedApp, remotes));
5137
+ writeFile(targetDir, `${resolvedApp.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, resolvedApp));
5138
+ writeFile(targetDir, `${resolvedApp.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(resolvedApp));
5139
+ writeFile(targetDir, `${resolvedApp.directory}/modern.config.ts`, createAppModernConfig(scope, resolvedApp));
5140
+ writeFile(targetDir, `${resolvedApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(resolvedApp, scope, remotes));
5141
+ writeJson(targetDir, `${resolvedApp.directory}/locales/en/translation.json`, createAppPublicLocaleMessages(resolvedApp, 'en', remotes));
5142
+ writeJson(targetDir, `${resolvedApp.directory}/locales/en/${appI18nNamespace(resolvedApp)}.json`, createAppPublicLocaleMessages(resolvedApp, 'en', remotes));
5143
+ writeJson(targetDir, `${resolvedApp.directory}/locales/cs/translation.json`, createAppPublicLocaleMessages(resolvedApp, 'cs', remotes));
5144
+ writeJson(targetDir, `${resolvedApp.directory}/locales/cs/${appI18nNamespace(resolvedApp)}.json`, createAppPublicLocaleMessages(resolvedApp, 'cs', remotes));
5145
+ writeFile(targetDir, `${resolvedApp.directory}/src/routes/index.css`, createAppStyles(enableTailwind, scope, resolvedApp));
5461
5146
  if (enableTailwind) {
5462
- writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
5463
- writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
5147
+ writeFile(targetDir, `${resolvedApp.directory}/postcss.config.mjs`, createPostcssConfig());
5148
+ writeFile(targetDir, `${resolvedApp.directory}/tailwind.config.ts`, createTailwindConfig());
5464
5149
  }
5465
- writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig(scope) : createRemoteModuleFederationConfig(scope, app));
5466
- writeAppFile('src/routes/layout.tsx', createLayout(app.id));
5467
- for (const [relativePath, content] of Object.entries(commerceAssetsForApp(app)))writeFile(targetDir, `${app.directory}/${relativePath}`, content);
5468
- writeAppFile('src/routes/[lang]/page.tsx', 'shell' === app.kind ? createShellPage() : createRemotePage(app));
5469
- for (const route of createRouteOwnedI18nPaths(app))if ('/' !== route.canonicalPath && 'shell' !== app.kind) writeFile(targetDir, createRoutePageFilePath(app, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
5470
- if ('shell' === app.kind) {
5471
- writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(scope));
5150
+ writeFile(targetDir, `${resolvedApp.directory}/module-federation.config.ts`, 'shell' === resolvedApp.kind ? createShellModuleFederationConfig(scope, remotes) : createRemoteModuleFederationConfig(scope, resolvedApp, remotes));
5151
+ writeAppFile('src/routes/layout.tsx', createLayout(resolvedApp.id));
5152
+ for (const [relativePath, content] of Object.entries(workspaceAssetsForApp(resolvedApp)))writeFile(targetDir, `${resolvedApp.directory}/${relativePath}`, content);
5153
+ writeAppFile('src/routes/[lang]/page.tsx', 'shell' === resolvedApp.kind ? createShellPage(remotes) : createRemotePage(resolvedApp));
5154
+ for (const route of createRouteOwnedI18nPaths(resolvedApp))if ('/' !== route.canonicalPath && 'shell' !== resolvedApp.kind) writeFile(targetDir, createRoutePageFilePath(resolvedApp, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
5155
+ if ('shell' === resolvedApp.kind) {
5156
+ writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(scope, remotes));
5472
5157
  writeAppFile('src/routes/shell-frame.tsx', createShellFrameComponent());
5473
- writeAppFile('src/routes/boundary-overlay.tsx', createShellBoundaryOverlay());
5474
- writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
5475
- writeAppFile('src/routes/[lang]/tractors/page.tsx', createShellTractorsPage());
5476
- writeAppFile('src/routes/[lang]/stores/page.tsx', createShellStoresPage());
5477
- writeAppFile('src/routes/[lang]/tractors/[slug]/page.tsx', createShellProductPage());
5478
- writeAppFile('src/routes/[lang]/cart/page.tsx', createShellCartPage());
5158
+ writeFile(targetDir, `${resolvedApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
5479
5159
  }
5480
- if (appHasEffectApi(app)) {
5481
- writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
5482
- writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
5483
- writeFile(targetDir, `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`, createEffectClient(app, '../../shared/effect/api'));
5160
+ if (appHasEffectApi(resolvedApp)) {
5161
+ writeFile(targetDir, `${resolvedApp.directory}/shared/effect/api.ts`, createEffectSharedApi(resolvedApp));
5162
+ writeFile(targetDir, `${resolvedApp.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, resolvedApp, '../../shared/effect/api'));
5163
+ writeFile(targetDir, `${resolvedApp.directory}/src/effect/${resolvedApp.effectApi.stem}-client.ts`, createEffectClient(resolvedApp, '../../shared/effect/api'));
5484
5164
  }
5485
- if ('vertical' === app.kind) {
5486
- writeAppFile('src/federation-entry.tsx', createRemoteEntry(app));
5487
- if ('decide' === app.id) writeAppFile('src/components/vertical-components.tsx', createDecideRemoteComponents(scope, app));
5488
- if ('checkout' === app.id) writeFile(targetDir, `${app.directory}/src/cart-store.ts`, createCheckoutCartStore());
5489
- for (const expose of Object.keys(app.exposes ?? {})){
5490
- const outputPath = remoteComponentOutputPath(app, expose);
5491
- if (outputPath) writeAppFile(outputPath.slice(app.directory.length + 1), createRemoteExposeComponent(app, expose));
5165
+ if ('vertical' === resolvedApp.kind) {
5166
+ writeAppFile('src/federation-entry.tsx', createRemoteEntry(resolvedApp));
5167
+ if ('records' === resolvedApp.id) writeAppFile('src/components/vertical-components.tsx', createRecordsRemoteComponents(scope, resolvedApp));
5168
+ if ('actions' === resolvedApp.id) writeFile(targetDir, `${resolvedApp.directory}/src/action-queue-store.ts`, createActionQueueStore());
5169
+ for (const expose of Object.keys(resolvedApp.exposes ?? {})){
5170
+ const outputPath = remoteComponentOutputPath(resolvedApp, expose);
5171
+ if (outputPath) writeAppFile(outputPath.slice(resolvedApp.directory.length + 1), createRemoteExposeComponent(resolvedApp, expose));
5492
5172
  }
5493
5173
  }
5494
- if ('horizontal-design-system' === app.kind) {
5495
- writeAppFile('src/components/button.tsx', createDesignButton(app));
5496
- writeFile(targetDir, `${app.directory}/src/tokens.ts`, createDesignTokens());
5497
- }
5498
5174
  }
5499
5175
  function writeSharedPackages(targetDir, scope, packageSource) {
5500
5176
  for (const sharedPackage of sharedPackages){
@@ -5578,17 +5254,29 @@ function nextAvailablePort(ports) {
5578
5254
  function assertCanCreate(workspaceRoot, relativePath) {
5579
5255
  if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
5580
5256
  }
5581
- function addRootDevScript(workspaceRoot, scope, packageSuffix, scriptName) {
5257
+ function updateRootWorkspaceScripts(workspaceRoot, scope, packageSource, remotes) {
5582
5258
  const packagePath = node_path.join(workspaceRoot, 'package.json');
5583
5259
  const rootPackage = readJsonFile(packagePath);
5584
- rootPackage.scripts ??= {};
5585
- rootPackage.scripts[`dev:${scriptName}`] = `pnpm --filter ${ultramodern_workspace_packageName(scope, packageSuffix)} dev`;
5586
- if ('string' == typeof rootPackage.scripts.dev && !rootPackage.scripts.dev.includes(ultramodern_workspace_packageName(scope, packageSuffix))) {
5587
- const packageFilter = `--filter ${ultramodern_workspace_packageName(scope, packageSuffix)}`;
5588
- rootPackage.scripts.dev = rootPackage.scripts.dev.endsWith(' dev') ? rootPackage.scripts.dev.replace(/ dev$/u, ` ${packageFilter} dev`) : `${rootPackage.scripts.dev} ${packageFilter}`;
5589
- }
5260
+ const generatedRootPackage = createRootPackageJson(scope, packageSource, remotes);
5261
+ rootPackage.scripts = generatedRootPackage.scripts;
5590
5262
  writeJsonFile(packagePath, rootPackage);
5591
5263
  }
5264
+ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwind, remotes) {
5265
+ const shellHost = createShellHost(remotes);
5266
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
5267
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
5268
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(shellHost));
5269
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(shellHost, scope, remotes));
5270
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/translation.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
5271
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
5272
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/translation.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
5273
+ writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/cs/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'cs', remotes));
5274
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(scope, remotes));
5275
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/[lang]/page.tsx`, createShellPage(remotes));
5276
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/vertical-components.tsx`, createShellRemoteComponents(scope, remotes));
5277
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
5278
+ writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/effect/vertical-clients.ts`, createShellEffectClient(scope, remotes));
5279
+ }
5592
5280
  function addShellZephyrDependency(workspaceRoot, scope, remote) {
5593
5281
  const packagePath = node_path.join(workspaceRoot, shellApp.directory, 'package.json');
5594
5282
  const shellPackage = readJsonFile(packagePath);
@@ -5604,7 +5292,7 @@ function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
5604
5292
  shellPackage.dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
5605
5293
  writeJsonFile(packagePath, shellPackage);
5606
5294
  }
5607
- function verticalTopologyEntry(scope, vertical) {
5295
+ function verticalTopologyEntry(scope, vertical, remotes = []) {
5608
5296
  return {
5609
5297
  id: vertical.id,
5610
5298
  kind: vertical.kind,
@@ -5618,7 +5306,7 @@ function verticalTopologyEntry(scope, vertical) {
5618
5306
  exposes: Object.keys(vertical.exposes ?? {}),
5619
5307
  ...vertical.verticalRefs?.length ? {
5620
5308
  verticalRefs: vertical.verticalRefs,
5621
- remotes: createModuleFederationRemoteContracts(vertical)
5309
+ remotes: createModuleFederationRemoteContracts(vertical, remotes)
5622
5310
  } : {},
5623
5311
  ssr: true,
5624
5312
  fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
@@ -5658,13 +5346,13 @@ function verticalsFromTopology(topology, ports) {
5658
5346
  displayName: vertical.displayName ?? `${toPascalCase(domain)} Vertical`,
5659
5347
  kind: 'vertical',
5660
5348
  domain,
5661
- portEnv: '',
5349
+ portEnv: `VERTICAL_${toEnvSegment(domain)}_PORT`,
5662
5350
  port: 'number' == typeof ports[vertical.id] ? ports[vertical.id] : 0,
5663
5351
  mfName: vertical.moduleFederation?.name ?? `vertical${toPascalCase(domain)}`,
5664
5352
  ...Array.isArray(vertical.moduleFederation?.exposes) ? {
5665
5353
  exposes: Object.fromEntries(vertical.moduleFederation.exposes.map((expose)=>[
5666
5354
  expose,
5667
- ''
5355
+ './Route' === expose ? './src/federation-entry.tsx' : './Widget' === expose ? `./src/components/${domain}-widget.tsx` : ''
5668
5356
  ]))
5669
5357
  } : {},
5670
5358
  ...Array.isArray(vertical.moduleFederation?.verticalRefs) ? {
@@ -5737,21 +5425,20 @@ function addUltramodernVertical(options) {
5737
5425
  },
5738
5426
  ...updatedVerticals
5739
5427
  ], enableTailwind));
5740
- const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
5741
- writeFileReplacing(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(scope, updatedVerticals));
5742
- if (!node_fs.existsSync(shellConfigPath)) throw new Error('Shell Module Federation config was not regenerated');
5428
+ rewriteShellAppFiles(options.workspaceRoot, scope, packageSource, enableTailwind, updatedVerticals);
5743
5429
  writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
5744
5430
  addShellZephyrDependency(options.workspaceRoot, scope, vertical);
5745
5431
  addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
5746
- addRootDevScript(options.workspaceRoot, scope, vertical.packageSuffix, name);
5432
+ updateRootWorkspaceScripts(options.workspaceRoot, scope, packageSource, updatedVerticals);
5747
5433
  }
5748
5434
  function generateUltramodernWorkspace(options) {
5749
5435
  const scope = toPackageScope(options.packageName);
5750
5436
  const packageSource = resolvePackageSource(options);
5751
5437
  const enableTailwind = false !== options.enableTailwind;
5438
+ const initialVerticals = [];
5752
5439
  assertUniqueTailwindPrefixes([
5753
5440
  shellApp,
5754
- ...verticalApps
5441
+ ...initialVerticals
5755
5442
  ]);
5756
5443
  node_fs.mkdirSync(options.targetDir, {
5757
5444
  recursive: true
@@ -5762,21 +5449,21 @@ function generateUltramodernWorkspace(options) {
5762
5449
  pnpmVersion: PNPM_VERSION,
5763
5450
  tailwindEnabled: String(enableTailwind)
5764
5451
  });
5765
- writeJson(options.targetDir, 'package.json', createRootPackageJson(scope, packageSource));
5452
+ writeJson(options.targetDir, 'package.json', createRootPackageJson(scope, packageSource, initialVerticals));
5766
5453
  writeJson(options.targetDir, 'tsconfig.base.json', createTsConfigBase());
5767
- writeJson(options.targetDir, 'topology/reference-topology.json', createTopology(scope));
5768
- writeJson(options.targetDir, 'topology/ownership.json', createOwnership(scope));
5769
- writeJson(options.targetDir, 'topology/local-overlays/development.json', createDevelopmentOverlay());
5454
+ writeJson(options.targetDir, 'topology/reference-topology.json', createTopology(scope, initialVerticals));
5455
+ writeJson(options.targetDir, 'topology/ownership.json', createOwnership(scope, initialVerticals));
5456
+ writeJson(options.targetDir, 'topology/local-overlays/development.json', createDevelopmentOverlay(initialVerticals));
5770
5457
  writeJson(options.targetDir, '.modernjs/ultramodern-workspace-template-manifest.json', createTemplateManifest(options.modernVersion, packageSource));
5771
5458
  writeJson(options.targetDir, '.modernjs/ultramodern-package-source.json', createPackageSourceMetadata(scope, packageSource));
5772
5459
  writeJson(options.targetDir, GENERATED_CONTRACT_PATH, createGeneratedContract(scope, [
5773
- shellApp,
5774
- ...verticalApps
5460
+ createShellHost(initialVerticals),
5461
+ ...initialVerticals
5775
5462
  ], enableTailwind));
5776
- writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
5777
- for (const remote of verticalApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
5463
+ writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind, initialVerticals);
5464
+ for (const remote of initialVerticals)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind, initialVerticals);
5778
5465
  writeSharedPackages(options.targetDir, scope, packageSource);
5779
- writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind);
5466
+ writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind, initialVerticals);
5780
5467
  }
5781
5468
  const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
5782
5469
  const templateDir = node_path.resolve(src_dirname, '..', 'template');
@@ -6196,6 +5883,7 @@ function showHelp() {
6196
5883
  if (localeKeys.help.example9) console.log(i18n.t(localeKeys.help.example9));
6197
5884
  if (localeKeys.help.example10) console.log(i18n.t(localeKeys.help.example10));
6198
5885
  if (localeKeys.help.example11) console.log(i18n.t(localeKeys.help.example11));
5886
+ if (localeKeys.help.example12) console.log(i18n.t(localeKeys.help.example12));
6199
5887
  console.log('');
6200
5888
  console.log(i18n.t(localeKeys.help.moreInfo));
6201
5889
  console.log('');
@@ -6240,9 +5928,9 @@ function detectVerticalFlag() {
6240
5928
  }
6241
5929
  return args.includes('--vertical');
6242
5930
  }
6243
- function detectUltramodernWorkspaceFlag(createPackage) {
5931
+ function detectUltramodernWorkspaceFlag() {
6244
5932
  const args = process.argv.slice(2);
6245
- return args.includes(ULTRAMODERN_WORKSPACE_FLAG) || isBleedingDevCreatePackage(createPackage);
5933
+ return args.includes(ULTRAMODERN_WORKSPACE_FLAG);
6246
5934
  }
6247
5935
  function detectUltramodernPackageSource(args, defaultPackageVersion, createPackage) {
6248
5936
  const bleedingDevDefaults = isBleedingDevCreatePackage(createPackage);
@@ -6429,7 +6117,7 @@ async function main() {
6429
6117
  process.exit(1);
6430
6118
  }
6431
6119
  }
6432
- const generateWorkspace = detectUltramodernWorkspaceFlag(createPackage);
6120
+ const generateWorkspace = detectUltramodernWorkspaceFlag();
6433
6121
  if (generateWorkspace) {
6434
6122
  generateUltramodernWorkspace({
6435
6123
  targetDir,