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

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
@@ -250,8 +250,8 @@ ListCache.prototype.get = _listCacheGet;
250
250
  ListCache.prototype.has = _listCacheHas;
251
251
  ListCache.prototype.set = _listCacheSet;
252
252
  const _ListCache = ListCache;
253
- var Map = _getNative(_root, 'Map');
254
- const _Map = Map;
253
+ var _Map_Map = _getNative(_root, 'Map');
254
+ const _Map = _Map_Map;
255
255
  function mapCacheClear() {
256
256
  this.size = 0;
257
257
  this.__data__ = {
@@ -453,8 +453,8 @@ const EN_LOCALE = {
453
453
  success: '✨ Created successfully!',
454
454
  nextSteps: '📋 Next steps:',
455
455
  step1: 'cd {projectName}',
456
- step2: 'mise exec -- pnpm install',
457
- step3: 'mise exec -- pnpm dev'
456
+ step2: 'pnpm install',
457
+ step3: 'pnpm dev'
458
458
  },
459
459
  help: {
460
460
  title: '🚀 Modern.js Project Creator',
@@ -474,7 +474,7 @@ const EN_LOCALE = {
474
474
  optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install)',
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
- optionMicroVertical: ' --microvertical Add a MicroVertical package to an existing UltraModern workspace (remote, horizontal-remote, service, shared)',
477
+ optionVertical: ' --vertical Add a full-stack vertical to an existing UltraModern workspace',
478
478
  optionSub: ' -s, --sub Mark as a subproject (package in monorepo)',
479
479
  examples: '💡 Examples:',
480
480
  example1: ' create my-app',
@@ -487,7 +487,7 @@ const EN_LOCALE = {
487
487
  example8: ' create my-app --router tanstack --bff-runtime effect',
488
488
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
489
489
  example10: ' pnpm dlx @bleedingdev/modern-js-create my-super-app',
490
- example11: ' create catalog --microvertical remote',
490
+ example11: ' create catalog --vertical',
491
491
  moreInfo: '📚 Learn more: https://modernjs.dev'
492
492
  },
493
493
  version: {
@@ -510,8 +510,8 @@ const ZH_LOCALE = {
510
510
  success: '✨ 创建成功!',
511
511
  nextSteps: '📋 下一步:',
512
512
  step1: 'cd {projectName}',
513
- step2: 'mise exec -- pnpm install',
514
- step3: 'mise exec -- pnpm dev'
513
+ step2: 'pnpm install',
514
+ step3: 'pnpm dev'
515
515
  },
516
516
  help: {
517
517
  title: '🚀 Modern.js 项目创建工具',
@@ -531,7 +531,7 @@ const ZH_LOCALE = {
531
531
  optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install)',
532
532
  optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
533
533
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
534
- optionMicroVertical: ' --microvertical 向现有 UltraModern 工作区添加 MicroVertical 包(remote、horizontal-remote、service、shared)',
534
+ optionVertical: ' --vertical 向现有 UltraModern 工作区添加全栈 Vertical',
535
535
  optionSub: ' -s, --sub 标记为子项目(monorepo 中的子包)',
536
536
  examples: '💡 示例:',
537
537
  example1: ' create my-app',
@@ -544,7 +544,7 @@ const ZH_LOCALE = {
544
544
  example8: ' create my-app --router tanstack --bff-runtime effect',
545
545
  example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
546
546
  example10: ' create my-super-app --ultramodern-workspace --ultramodern-package-source install --ultramodern-package-scope bleedingdev',
547
- example11: ' create catalog --microvertical remote',
547
+ example11: ' create catalog --vertical',
548
548
  moreInfo: '📚 更多信息: https://modernjs.dev'
549
549
  },
550
550
  version: {
@@ -571,11 +571,12 @@ const TYPESCRIPT_NATIVE_PREVIEW_VERSION = '7.0.0-dev.20260527.2';
571
571
  const OXLINT_VERSION = '1.66.0';
572
572
  const OXFMT_VERSION = '0.51.0';
573
573
  const ULTRACITE_VERSION = '7.7.0';
574
+ const LEFTHOOK_VERSION = '^2.1.9';
574
575
  const I18NEXT_VERSION = '26.2.0';
575
576
  const REACT_VERSION = '^19.2.6';
576
577
  const REACT_DOM_VERSION = '^19.2.6';
577
578
  const REACT_ROUTER_DOM_VERSION = '7.16.0';
578
- const PNPM_VERSION = '11.4.0';
579
+ const PNPM_VERSION = '11.5.0';
579
580
  const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
580
581
  const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
581
582
  const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
@@ -620,10 +621,10 @@ const shellApp = {
620
621
  portEnv: 'SHELL_SUPER_APP_PORT',
621
622
  port: 3020,
622
623
  mfName: 'shellSuperApp',
623
- remoteRefs: [
624
- 'remote-explore',
625
- 'remote-decide',
626
- 'remote-checkout'
624
+ verticalRefs: [
625
+ 'explore',
626
+ 'decide',
627
+ 'checkout'
627
628
  ],
628
629
  ownership: {
629
630
  team: 'super-app-platform',
@@ -640,22 +641,22 @@ const shellApp = {
640
641
  }
641
642
  }
642
643
  };
643
- const remoteApps = [
644
+ const verticalApps = [
644
645
  {
645
- id: 'remote-explore',
646
- directory: 'apps/remotes/remote-explore',
647
- packageSuffix: 'remote-explore',
648
- displayName: 'Explore Remote',
646
+ id: 'explore',
647
+ directory: 'verticals/explore',
648
+ packageSuffix: 'explore',
649
+ displayName: 'Explore Vertical',
649
650
  kind: 'vertical',
650
651
  domain: 'explore',
651
- portEnv: 'REMOTE_EXPLORE_PORT',
652
+ portEnv: 'VERTICAL_EXPLORE_PORT',
652
653
  port: 3021,
653
- mfName: 'remoteExplore',
654
+ mfName: 'verticalExplore',
654
655
  exposes: {
655
656
  './Footer': './src/components/footer.tsx',
656
657
  './Header': './src/components/header.tsx',
657
658
  './Recommendations': './src/components/recommendations.tsx',
658
- './Route': './src/remote-entry.tsx',
659
+ './Route': './src/federation-entry.tsx',
659
660
  './StorePicker': './src/components/store-picker.tsx'
660
661
  },
661
662
  effectApi: {
@@ -663,15 +664,15 @@ const remoteApps = [
663
664
  prefix: '/explore-api',
664
665
  consumedBy: [
665
666
  shellApp.id,
666
- 'remote-explore'
667
+ 'explore'
667
668
  ]
668
669
  },
669
670
  ownership: {
670
671
  team: 'tractor-explore',
671
672
  slack: '#tractor-explore',
672
673
  pagerDuty: 'pd-tractor-explore',
673
- runbookRef: 'runbooks/wave2/remote-explore.md',
674
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-explore',
674
+ runbookRef: 'runbooks/wave2/explore.md',
675
+ adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#explore',
675
676
  blastRadius: {
676
677
  tier: 'tier-1-tractor-discovery',
677
678
  references: [
@@ -682,37 +683,37 @@ const remoteApps = [
682
683
  }
683
684
  },
684
685
  {
685
- id: 'remote-decide',
686
- directory: 'apps/remotes/remote-decide',
687
- packageSuffix: 'remote-decide',
688
- displayName: 'Decide Remote',
686
+ id: 'decide',
687
+ directory: 'verticals/decide',
688
+ packageSuffix: 'decide',
689
+ displayName: 'Decide Vertical',
689
690
  kind: 'vertical',
690
691
  domain: 'decide',
691
- portEnv: 'REMOTE_DECIDE_PORT',
692
+ portEnv: 'VERTICAL_DECIDE_PORT',
692
693
  port: 3022,
693
- mfName: 'remoteDecide',
694
- remoteRefs: [
695
- 'remote-explore',
696
- 'remote-checkout'
694
+ mfName: 'verticalDecide',
695
+ verticalRefs: [
696
+ 'explore',
697
+ 'checkout'
697
698
  ],
698
699
  exposes: {
699
700
  './ProductPage': './src/components/product-page.tsx',
700
- './Route': './src/remote-entry.tsx'
701
+ './Route': './src/federation-entry.tsx'
701
702
  },
702
703
  effectApi: {
703
704
  stem: 'decide',
704
705
  prefix: '/decide-api',
705
706
  consumedBy: [
706
707
  shellApp.id,
707
- 'remote-decide'
708
+ 'decide'
708
709
  ]
709
710
  },
710
711
  ownership: {
711
712
  team: 'tractor-decide',
712
713
  slack: '#tractor-decide',
713
714
  pagerDuty: 'pd-tractor-decide',
714
- runbookRef: 'runbooks/wave2/remote-decide.md',
715
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-decide',
715
+ runbookRef: 'runbooks/wave2/decide.md',
716
+ adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#decide',
716
717
  blastRadius: {
717
718
  tier: 'tier-1-tractor-configuration',
718
719
  references: [
@@ -723,21 +724,21 @@ const remoteApps = [
723
724
  }
724
725
  },
725
726
  {
726
- id: 'remote-checkout',
727
- directory: 'apps/remotes/remote-checkout',
728
- packageSuffix: 'remote-checkout',
729
- displayName: 'Checkout Remote',
727
+ id: 'checkout',
728
+ directory: 'verticals/checkout',
729
+ packageSuffix: 'checkout',
730
+ displayName: 'Checkout Vertical',
730
731
  kind: 'vertical',
731
732
  domain: 'checkout',
732
- portEnv: 'REMOTE_CHECKOUT_PORT',
733
+ portEnv: 'VERTICAL_CHECKOUT_PORT',
733
734
  port: 3023,
734
- mfName: 'remoteCheckout',
735
+ mfName: 'verticalCheckout',
735
736
  exposes: {
736
737
  './AddToCart': './src/components/add-to-cart.tsx',
737
738
  './CartPage': './src/components/cart-page.tsx',
738
739
  './CheckoutPage': './src/components/checkout-page.tsx',
739
740
  './MiniCart': './src/components/mini-cart.tsx',
740
- './Route': './src/remote-entry.tsx',
741
+ './Route': './src/federation-entry.tsx',
741
742
  './ThanksPage': './src/components/thanks-page.tsx'
742
743
  },
743
744
  effectApi: {
@@ -745,15 +746,15 @@ const remoteApps = [
745
746
  prefix: '/checkout-api',
746
747
  consumedBy: [
747
748
  shellApp.id,
748
- 'remote-checkout'
749
+ 'checkout'
749
750
  ]
750
751
  },
751
752
  ownership: {
752
753
  team: 'tractor-checkout',
753
754
  slack: '#tractor-checkout',
754
755
  pagerDuty: 'pd-tractor-checkout',
755
- runbookRef: 'runbooks/wave2/remote-checkout.md',
756
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-checkout',
756
+ runbookRef: 'runbooks/wave2/checkout.md',
757
+ adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#checkout',
757
758
  blastRadius: {
758
759
  tier: 'tier-1-tractor-purchase',
759
760
  references: [
@@ -764,27 +765,6 @@ const remoteApps = [
764
765
  }
765
766
  }
766
767
  ];
767
- const effectService = {
768
- id: 'service-recommendations-effect',
769
- directory: 'services/service-recommendations-effect',
770
- packageSuffix: 'service-recommendations-effect',
771
- portEnv: 'SERVICE_RECOMMENDATIONS_PORT',
772
- port: 3030,
773
- ownership: {
774
- team: 'personalization-platform',
775
- slack: '#personalization-platform',
776
- pagerDuty: 'pd-personalization-platform',
777
- runbookRef: 'runbooks/wave2/service-recommendations-effect.md',
778
- adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#service-recommendations-effect',
779
- blastRadius: {
780
- tier: 'tier-2-personalization',
781
- references: [
782
- 'docs/super-app-rfc-adr/wave2/blast-radius.md#recommendations',
783
- 'docs/super-app-rfc-adr/wave2/rollback.md#effect-service-lkg'
784
- ]
785
- }
786
- }
787
- };
788
768
  const effectDiagnostics = [
789
769
  'anyUnknownInErrorContext',
790
770
  'classSelfMismatch',
@@ -869,21 +849,21 @@ const sharedPackages = [
869
849
  {
870
850
  id: 'shared-design-tokens',
871
851
  directory: 'packages/shared-design-tokens',
872
- description: 'Design token placeholders consumed by shell and remotes.'
852
+ description: 'Design token placeholders consumed by shell and verticals.'
873
853
  },
874
854
  {
875
855
  id: 'shared-effect-api',
876
856
  directory: 'packages/shared-effect-api',
877
- description: 'Shared Effect API type placeholders for services and clients.'
857
+ description: 'Shared Effect API type placeholders for vertical clients.'
878
858
  }
879
859
  ];
880
- function createNeutralOwnership(id, tier = 'tier-2-microvertical') {
860
+ function createNeutralOwnership(id, tier = 'tier-2-vertical') {
881
861
  return {
882
862
  team: 'super-app-platform',
883
863
  slack: '#super-app-platform',
884
864
  pagerDuty: 'pd-super-app-platform',
885
- runbookRef: `runbooks/microverticals/${id}.md`,
886
- adrRef: `docs/super-app-rfc-adr/microverticals.md#${id}`,
865
+ runbookRef: `runbooks/verticals/${id}.md`,
866
+ adrRef: `docs/super-app-rfc-adr/verticals.md#${id}`,
887
867
  blastRadius: {
888
868
  tier,
889
869
  references: [
@@ -892,49 +872,35 @@ function createNeutralOwnership(id, tier = 'tier-2-microvertical') {
892
872
  }
893
873
  };
894
874
  }
895
- function createRemoteDescriptor(name, kind, port) {
875
+ function createVerticalDescriptor(name, port) {
896
876
  const domain = toKebabCase(name);
897
- const id = `remote-${domain}`;
877
+ const id = domain;
898
878
  const displayPrefix = toPascalCase(domain).replace(/([a-z])([A-Z])/g, '$1 $2');
899
879
  return {
900
880
  id,
901
- directory: `apps/remotes/${id}`,
902
- packageSuffix: id,
903
- displayName: `${displayPrefix} Remote`,
904
- kind: 'horizontal-remote' === kind ? 'horizontal-remote' : 'vertical',
881
+ directory: `verticals/${domain}`,
882
+ packageSuffix: domain,
883
+ displayName: `${displayPrefix} Vertical`,
884
+ kind: 'vertical',
905
885
  domain,
906
- portEnv: `REMOTE_${toEnvSegment(domain)}_PORT`,
886
+ portEnv: `VERTICAL_${toEnvSegment(domain)}_PORT`,
907
887
  port,
908
- mfName: `remote${toPascalCase(domain)}`,
888
+ mfName: `vertical${toPascalCase(domain)}`,
909
889
  exposes: {
910
- './Route': './src/remote-entry.tsx',
890
+ './Route': './src/federation-entry.tsx',
911
891
  './Widget': `./src/components/${domain}-widget.tsx`
912
892
  },
913
- ...'remote' === kind ? {
914
- effectApi: {
915
- stem: domain,
916
- prefix: `/${domain}-api`,
917
- consumedBy: [
918
- shellApp.id,
919
- id
920
- ]
921
- }
922
- } : {},
893
+ effectApi: {
894
+ stem: domain,
895
+ prefix: `/${domain}-api`,
896
+ consumedBy: [
897
+ shellApp.id,
898
+ id
899
+ ]
900
+ },
923
901
  ownership: createNeutralOwnership(id)
924
902
  };
925
903
  }
926
- function createServiceDescriptor(name, port) {
927
- const normalized = toKebabCase(name);
928
- const suffix = normalized.endsWith('-effect') ? normalized : `service-${normalized.replace(/^service-/, '')}-effect`;
929
- return {
930
- id: suffix,
931
- directory: `services/${suffix}`,
932
- packageSuffix: suffix,
933
- portEnv: `${toEnvSegment(suffix)}_PORT`,
934
- port,
935
- ownership: createNeutralOwnership(suffix, 'tier-2-effect-service')
936
- };
937
- }
938
904
  function serviceApiPrefix(service) {
939
905
  const name = service.id.replace(/^service-/, '').replace(/-effect$/, '');
940
906
  return name.endsWith('-api') ? `/${name}` : `/${name}-api`;
@@ -948,18 +914,9 @@ function effectApiPrefix(target) {
948
914
  function effectApiStem(target) {
949
915
  return target.effectApi?.stem ?? target.id.replace(/^service-/, '').replace(/-effect$/, '').replace(/-api$/, '');
950
916
  }
951
- function verticalEffectApps(remotes = remoteApps) {
917
+ function verticalEffectApps(remotes = verticalApps) {
952
918
  return remotes.filter(appHasEffectApi);
953
919
  }
954
- function createSharedPackageDescriptor(name) {
955
- const normalized = toKebabCase(name);
956
- const id = normalized.startsWith('shared-') ? normalized : `shared-${normalized}`;
957
- return {
958
- id,
959
- directory: `packages/${id}`,
960
- description: `Shared ${normalized.replace(/^shared-/, '')} package placeholder.`
961
- };
962
- }
963
920
  function normalizePath(filePath) {
964
921
  return filePath.split(node_path.sep).join('/');
965
922
  }
@@ -1125,19 +1082,19 @@ function createRootPackageJson(scope, packageSource) {
1125
1082
  type: 'module',
1126
1083
  packageManager: `pnpm@${PNPM_VERSION}`,
1127
1084
  scripts: {
1128
- dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'remote-explore')} --filter ${ultramodern_workspace_packageName(scope, 'remote-decide')} --filter ${ultramodern_workspace_packageName(scope, 'remote-checkout')} dev`,
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`,
1129
1086
  'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
1130
- 'dev:explore': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-explore')} dev`,
1131
- 'dev:decide': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-decide')} dev`,
1132
- 'dev:checkout': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-checkout')} dev`,
1133
- build: 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
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',
1134
1091
  format: 'oxfmt .',
1135
1092
  'format:check': 'oxfmt --check .',
1136
1093
  lint: 'oxlint .',
1137
1094
  'lint:fix': 'oxlint . --fix',
1138
1095
  typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
1139
- 'cloudflare:build': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
1140
- 'cloudflare:deploy': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy',
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',
1141
1098
  'cloudflare:proof': "node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json",
1142
1099
  'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
1143
1100
  'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
@@ -1145,17 +1102,16 @@ function createRootPackageJson(scope, packageSource) {
1145
1102
  'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
1146
1103
  'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
1147
1104
  'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
1148
- postinstall: "node ./scripts/setup-agent-reference-repos.mjs && node ./scripts/bootstrap-agent-skills.mjs",
1105
+ postinstall: "node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true) && node ./scripts/setup-agent-reference-repos.mjs",
1149
1106
  check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:check'
1150
1107
  },
1151
1108
  engines: {
1152
1109
  node: '>=20',
1153
- pnpm: `>=${PNPM_VERSION} <11.5.0`
1110
+ pnpm: `>=${PNPM_VERSION} <11.6.0`
1154
1111
  },
1155
1112
  workspaces: [
1156
1113
  'apps/*',
1157
- 'apps/remotes/*',
1158
- 'services/*',
1114
+ 'verticals/*',
1159
1115
  'packages/*'
1160
1116
  ],
1161
1117
  modernjs: {
@@ -1171,6 +1127,7 @@ function createRootPackageJson(scope, packageSource) {
1171
1127
  devDependencies: {
1172
1128
  '@effect/tsgo': EFFECT_TSGO_VERSION,
1173
1129
  "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
1130
+ lefthook: LEFTHOOK_VERSION,
1174
1131
  oxlint: OXLINT_VERSION,
1175
1132
  oxfmt: OXFMT_VERSION,
1176
1133
  ultracite: ULTRACITE_VERSION,
@@ -1185,11 +1142,11 @@ function remoteDependencyAlias(remote) {
1185
1142
  function zephyrRemoteDependency(scope, remote) {
1186
1143
  return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
1187
1144
  }
1188
- function resolveRemoteRefs(app, remotes = remoteApps) {
1189
- const remoteRefs = app.remoteRefs ?? [];
1190
- return remoteRefs.map((remoteRef)=>remotes.find((remote)=>remote.id === remoteRef)).filter((remote)=>void 0 !== remote);
1145
+ function resolveRemoteRefs(app, remotes = verticalApps) {
1146
+ const verticalRefs = app.verticalRefs ?? [];
1147
+ return verticalRefs.map((remoteRef)=>remotes.find((remote)=>remote.id === remoteRef)).filter((remote)=>void 0 !== remote);
1191
1148
  }
1192
- function createModuleFederationRemoteContracts(app, remotes = remoteApps) {
1149
+ function createModuleFederationRemoteContracts(app, remotes = verticalApps) {
1193
1150
  return resolveRemoteRefs(app, remotes).map((remote)=>({
1194
1151
  id: remote.id,
1195
1152
  alias: remoteDependencyAlias(remote),
@@ -1198,8 +1155,8 @@ function createModuleFederationRemoteContracts(app, remotes = remoteApps) {
1198
1155
  manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
1199
1156
  }));
1200
1157
  }
1201
- function createZephyrDependencies(scope, app, remotes = remoteApps) {
1202
- if (!app.remoteRefs?.length) return {};
1158
+ function createZephyrDependencies(scope, app, remotes = verticalApps) {
1159
+ if (!app.verticalRefs?.length) return {};
1203
1160
  return Object.fromEntries(resolveRemoteRefs(app, remotes).map((remote)=>[
1204
1161
  remoteDependencyAlias(remote),
1205
1162
  zephyrRemoteDependency(scope, remote)
@@ -1317,9 +1274,9 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
1317
1274
  scripts: {
1318
1275
  dev: 'modern dev',
1319
1276
  build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
1320
- 'cloudflare:build': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy',
1321
- 'cloudflare:deploy': 'MODERNJS_DEPLOY=cloudflare modern deploy',
1322
- 'cloudflare:preview': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy && wrangler dev --config .output/wrangler.json',
1277
+ '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',
1279
+ 'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
1323
1280
  'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mjs --app ${app.id}`,
1324
1281
  serve: 'modern serve',
1325
1282
  typecheck: effectTsgoTypecheckCommand
@@ -1347,46 +1304,6 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
1347
1304
  if (Object.keys(packageExports).length > 0) packageJson.exports = packageExports;
1348
1305
  return packageJson;
1349
1306
  }
1350
- function createServicePackage(scope, packageSource, enableTailwind, service = effectService) {
1351
- return {
1352
- private: true,
1353
- name: ultramodern_workspace_packageName(scope, service.packageSuffix),
1354
- version: '0.1.0',
1355
- scripts: {
1356
- dev: 'modern dev',
1357
- build: 'modern build',
1358
- serve: 'modern serve',
1359
- typecheck: effectTsgoTypecheckCommand
1360
- },
1361
- modernjs: {
1362
- preset: 'presetUltramodern',
1363
- role: 'effect-service',
1364
- appId: service.id,
1365
- topology: `${relativeRootFor(service.directory)}/topology/reference-topology.json`
1366
- },
1367
- dependencies: {
1368
- '@modern-js/runtime': modernPackageSpecifier('@modern-js/runtime', packageSource),
1369
- [ultramodern_workspace_packageName(scope, 'shared-effect-api')]: WORKSPACE_PACKAGE_VERSION,
1370
- react: REACT_VERSION,
1371
- 'react-dom': REACT_DOM_VERSION
1372
- },
1373
- devDependencies: {
1374
- '@modern-js/app-tools': modernPackageSpecifier('@modern-js/app-tools', packageSource),
1375
- '@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
1376
- '@effect/tsgo': EFFECT_TSGO_VERSION,
1377
- ...enableTailwind ? {
1378
- '@tailwindcss/postcss': `^${TAILWIND_POSTCSS_VERSION}`,
1379
- postcss: '^8.5.6',
1380
- tailwindcss: `^${TAILWIND_VERSION}`
1381
- } : {},
1382
- "@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION,
1383
- '@types/node': '^20',
1384
- '@types/react': '^19.1.8',
1385
- '@types/react-dom': '^19.1.6',
1386
- typescript: TYPESCRIPT_VERSION
1387
- }
1388
- };
1389
- }
1390
1307
  function createSharedPackage(scope, id, description, packageSource) {
1391
1308
  const packageJson = {
1392
1309
  private: true,
@@ -1465,11 +1382,36 @@ const zephyrRspackPlugin = () => ({
1465
1382
  const appId = '${app.id}';
1466
1383
  const cloudflareWorkerName = '${createCloudflareWorkerName(scope, app)}';
1467
1384
  const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
1468
- const configuredSiteUrl = process.env['MODERN_PUBLIC_SITE_URL']?.trim();
1469
- const configuredCloudflareUrl =
1470
- process.env['${createCloudflarePublicUrlEnv(app)}']?.trim();
1385
+ const envValue = (name: string) => {
1386
+ const value = process.env[name]?.trim();
1387
+ return value !== undefined && value.length > 0 ? value : undefined;
1388
+ };
1389
+ const configuredSiteUrl = envValue('MODERN_PUBLIC_SITE_URL');
1390
+ const configuredCloudflareUrl = envValue('${createCloudflarePublicUrlEnv(app)}');
1391
+ const cloudflareWorkersDevSubdomain = envValue(
1392
+ 'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
1393
+ );
1394
+ const inferredCloudflareUrl =
1395
+ cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined
1396
+ ? \`https://\${cloudflareWorkerName}.\${cloudflareWorkersDevSubdomain}.workers.dev\`
1397
+ : undefined;
1471
1398
  const siteUrl =
1472
- configuredSiteUrl || configuredCloudflareUrl || \`http://localhost:\${port}\`;
1399
+ configuredCloudflareUrl ||
1400
+ configuredSiteUrl ||
1401
+ inferredCloudflareUrl ||
1402
+ \`http://localhost:\${port}\`;
1403
+
1404
+ if (
1405
+ cloudflareDeployEnabled &&
1406
+ process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true' &&
1407
+ configuredCloudflareUrl === undefined &&
1408
+ configuredSiteUrl === undefined &&
1409
+ inferredCloudflareUrl === undefined
1410
+ ) {
1411
+ throw new Error(
1412
+ \`Cloudflare deploy for \${appId} needs ${createCloudflarePublicUrlEnv(app)}, MODERN_PUBLIC_SITE_URL, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN.\`,
1413
+ );
1414
+ }
1473
1415
 
1474
1416
  export default defineConfig(
1475
1417
  presetUltramodern(
@@ -1619,14 +1561,59 @@ ${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
1619
1561
  }`;
1620
1562
  }
1621
1563
  function createRemoteManifestEnv(remote) {
1622
- return `REMOTE_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
1564
+ return `VERTICAL_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
1565
+ }
1566
+ function createModuleFederationRemoteUrlHelpers(app, remotes = verticalApps) {
1567
+ if (0 === resolveRemoteRefs(app, remotes).length) return '';
1568
+ return `const cloudflareDeployEnabled =
1569
+ process.env['MODERNJS_DEPLOY'] === 'cloudflare';
1570
+ const cloudflareWorkersDevSubdomain =
1571
+ process.env['ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN']?.trim();
1572
+ const requireCloudflarePublicUrls =
1573
+ process.env['ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'] === 'true';
1574
+
1575
+ function createRemoteManifestUrl(options: {
1576
+ manifestEnv: string;
1577
+ publicUrlEnv: string;
1578
+ workerName: string;
1579
+ mfName: string;
1580
+ port: number;
1581
+ }) {
1582
+ const configuredManifest = process.env[options.manifestEnv]?.trim();
1583
+ if (configuredManifest !== undefined && configuredManifest.length > 0) {
1584
+ return configuredManifest;
1585
+ }
1586
+
1587
+ const configuredPublicUrl = process.env[options.publicUrlEnv]?.trim();
1588
+ if (configuredPublicUrl !== undefined && configuredPublicUrl.length > 0) {
1589
+ return \`\${options.mfName}@\${configuredPublicUrl.replace(/\\/+$/u, '')}/mf-manifest.json\`;
1590
+ }
1591
+
1592
+ if (cloudflareDeployEnabled && cloudflareWorkersDevSubdomain !== undefined) {
1593
+ return \`\${options.mfName}@https://\${options.workerName}.\${cloudflareWorkersDevSubdomain}.workers.dev/mf-manifest.json\`;
1594
+ }
1595
+
1596
+ if (cloudflareDeployEnabled && requireCloudflarePublicUrls) {
1597
+ throw new Error(
1598
+ \`Cloudflare deploy needs \${options.publicUrlEnv}, \${options.manifestEnv}, or ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN for remote \${options.mfName}.\`,
1599
+ );
1600
+ }
1601
+
1602
+ return \`\${options.mfName}@http://localhost:\${options.port}/mf-manifest.json\`;
1603
+ }
1604
+
1605
+ `;
1623
1606
  }
1624
- function createModuleFederationRemotesConfig(app, remotes = remoteApps) {
1607
+ function createModuleFederationRemotesConfig(scope, app, remotes = verticalApps) {
1625
1608
  const remoteEntries = resolveRemoteRefs(app, remotes).map((remote)=>{
1626
1609
  const key = remoteDependencyAlias(remote);
1627
- return ` ${key}:
1628
- process.env['${createRemoteManifestEnv(remote)}'] ??
1629
- '${remote.mfName}@http://localhost:${remote.port}/mf-manifest.json',`;
1610
+ return ` ${key}: createRemoteManifestUrl({
1611
+ manifestEnv: '${createRemoteManifestEnv(remote)}',
1612
+ publicUrlEnv: '${createCloudflarePublicUrlEnv(remote)}',
1613
+ workerName: '${createCloudflareWorkerName(scope, remote)}',
1614
+ mfName: '${remote.mfName}',
1615
+ port: ${remote.port},
1616
+ }),`;
1630
1617
  }).join('\n');
1631
1618
  if (!remoteEntries) return '';
1632
1619
  return ` remotes: {
@@ -1634,10 +1621,10 @@ ${remoteEntries}
1634
1621
  },
1635
1622
  `;
1636
1623
  }
1637
- function createShellModuleFederationConfig(remotes = remoteApps) {
1624
+ function createShellModuleFederationConfig(scope, remotes = verticalApps) {
1638
1625
  const shellHost = {
1639
1626
  ...shellApp,
1640
- remoteRefs: remotes.map((remote)=>remote.id)
1627
+ verticalRefs: remotes.map((remote)=>remote.id)
1641
1628
  };
1642
1629
  return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
1643
1630
  import { createRequire } from 'node:module';
@@ -1651,6 +1638,7 @@ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version:
1651
1638
  const reactVersion = (require('react/package.json') as { version: string }).version;
1652
1639
  const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
1653
1640
 
1641
+ ${createModuleFederationRemoteUrlHelpers(shellHost, remotes)}
1654
1642
  export default createModuleFederationConfig({
1655
1643
  treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
1656
1644
  dev: {
@@ -1664,7 +1652,7 @@ export default createModuleFederationConfig({
1664
1652
  },
1665
1653
  filename: 'remoteEntry.js',
1666
1654
  name: '${shellApp.mfName}',
1667
- ${createModuleFederationRemotesConfig(shellHost, remotes)}${createSharedModuleFederationConfig()},
1655
+ ${createModuleFederationRemotesConfig(scope, shellHost, remotes)}${createSharedModuleFederationConfig()},
1668
1656
  });
1669
1657
  `;
1670
1658
  }
@@ -1691,7 +1679,7 @@ export const ultramodernApiMarker = {
1691
1679
  } as const;
1692
1680
  `;
1693
1681
  }
1694
- function createRemoteModuleFederationConfig(app, remotes = remoteApps) {
1682
+ function createRemoteModuleFederationConfig(scope, app, remotes = verticalApps) {
1695
1683
  const exposes = formatTsObjectLiteral(app.exposes ?? {});
1696
1684
  return `// @effect-diagnostics nodeBuiltinImport:off
1697
1685
  import { createRequire } from 'node:module';
@@ -1705,6 +1693,7 @@ const runtimeVersion = (require('@modern-js/runtime/package.json') as { version:
1705
1693
  const reactVersion = (require('react/package.json') as { version: string }).version;
1706
1694
  const reactDomVersion = (require('react-dom/package.json') as { version: string }).version;
1707
1695
 
1696
+ ${createModuleFederationRemoteUrlHelpers(app, remotes)}
1708
1697
  export default createModuleFederationConfig({
1709
1698
  treeShakingSharedExcludePlugins: ['RspackModuleFederationPlugin'],
1710
1699
  dev: {
@@ -1719,7 +1708,7 @@ export default createModuleFederationConfig({
1719
1708
  exposes: ${exposes},
1720
1709
  filename: 'remoteEntry.js',
1721
1710
  name: '${app.mfName}',
1722
- ${createModuleFederationRemotesConfig(app, remotes)}${createSharedModuleFederationConfig()},
1711
+ ${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleFederationConfig()},
1723
1712
  });
1724
1713
  `;
1725
1714
  }
@@ -1754,6 +1743,16 @@ function createRouteOwnedI18nPaths(app) {
1754
1743
  },
1755
1744
  titleKey: 'shell.routes.listing'
1756
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
+ },
1757
1756
  {
1758
1757
  ...base,
1759
1758
  canonicalPath: '/tractors/:slug',
@@ -1984,7 +1983,7 @@ function createRouteAliasPage(canonicalPath) {
1984
1983
  return `export { default } from '${rootPageImport}';
1985
1984
  `;
1986
1985
  }
1987
- function createAppEnvDts(app, remotes = remoteApps) {
1986
+ function createAppEnvDts(app, remotes = verticalApps) {
1988
1987
  const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
1989
1988
  const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
1990
1989
  return `declare module '${moduleName}' {
@@ -2002,46 +2001,11 @@ declare module '*.svg' {
2002
2001
  }
2003
2002
  ${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
2004
2003
  }
2005
- function createServiceModernConfigFor(service = effectService) {
2006
- return `// @effect-diagnostics processEnv:off
2007
- import { appTools, defineConfig, presetUltramodern } from '@modern-js/app-tools';
2008
- import { bffPlugin } from '@modern-js/plugin-bff';
2009
-
2010
- const appId = '${service.id}';
2011
- const port = Number(process.env['${service.portEnv}'] ?? ${service.port});
2012
-
2013
- export default defineConfig(
2014
- presetUltramodern(
2015
- {
2016
- bff: {
2017
- effect: {
2018
- openapi: {
2019
- path: '/openapi.json',
2020
- },
2021
- },
2022
- prefix: '${serviceApiPrefix(service)}',
2023
- runtimeFramework: 'effect',
2024
- },
2025
- plugins: [appTools(), bffPlugin()],
2026
- server: {
2027
- port,
2028
- },
2029
- },
2030
- {
2031
- appId,
2032
- enableBffRequestId: true,
2033
- enableTelemetryExporters: true,
2034
- telemetryFailLoudStartup: false,
2035
- },
2036
- ),
2037
- );
2038
- `;
2039
- }
2040
2004
  function createAppRuntimeConfig(app) {
2041
2005
  const namespace = appI18nNamespace(app);
2042
2006
  const localeMessages = (language)=>{
2043
2007
  if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
2044
- return Object.assign({}, createAppLocaleMessages(app, language), ...remoteApps.map((remote)=>createAppLocaleMessages(remote, language)));
2008
+ return Object.assign({}, createAppLocaleMessages(app, language), ...verticalApps.map((remote)=>createAppLocaleMessages(remote, language)));
2045
2009
  };
2046
2010
  const resources = {
2047
2011
  cs: {
@@ -2082,14 +2046,44 @@ export default defineRuntimeConfig({
2082
2046
  function createCssTokenImport(scope) {
2083
2047
  return `@import '${ultramodern_workspace_packageName(scope, 'shared-design-tokens')}/tokens.css';\n`;
2084
2048
  }
2085
- function createShellStyles(enableTailwind, scope) {
2086
- return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}`;
2049
+ function createTailwindPrefix(raw) {
2050
+ const prefix = raw.toLowerCase().replace(/[^a-z]/gu, '');
2051
+ if (!prefix) throw new Error(`Cannot derive a Tailwind prefix from ${raw}`);
2052
+ return prefix;
2053
+ }
2054
+ function tailwindPrefixForApp(app) {
2055
+ if ('shell' === app.kind) return 'shell';
2056
+ return createTailwindPrefix(app.domain ?? app.id);
2057
+ }
2058
+ function tailwindPrefixForService(service) {
2059
+ return createTailwindPrefix(service.id);
2060
+ }
2061
+ function assertUniqueTailwindPrefixes(apps, services = []) {
2062
+ 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
+ ];
2073
+ for (const [id, prefix] of entries){
2074
+ const previous = seen.get(prefix);
2075
+ if (previous) throw new Error(`Tailwind prefix ${prefix} for ${id} collides with ${previous}`);
2076
+ seen.set(prefix, id);
2077
+ }
2087
2078
  }
2088
- function createRemoteStyles(enableTailwind, scope, _app) {
2089
- return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}`;
2079
+ function createTailwindImport(prefix) {
2080
+ return `@import 'tailwindcss' prefix(${prefix}) source(none);\n@source '..';\n`;
2090
2081
  }
2091
- function createServiceStyles(enableTailwind, scope, _service) {
2092
- return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}`;
2082
+ function createShellStyles(enableTailwind, scope) {
2083
+ return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(shellApp)) : ''}${createCssTokenImport(scope)}`;
2084
+ }
2085
+ function createRemoteStyles(enableTailwind, scope, app) {
2086
+ return `${enableTailwind ? createTailwindImport(tailwindPrefixForApp(app)) : ''}${createCssTokenImport(scope)}`;
2093
2087
  }
2094
2088
  function createAppStyles(enableTailwind, scope, app) {
2095
2089
  return 'shell' === app.kind ? createShellStyles(enableTailwind, scope) : createRemoteStyles(enableTailwind, scope, app);
@@ -2106,10 +2100,12 @@ function createTailwindConfig() {
2106
2100
  return `import type { Config } from 'tailwindcss';
2107
2101
 
2108
2102
  export default {
2109
- content: ['./src/**/*.{js,jsx,ts,tsx}'],
2110
2103
  } satisfies Config;
2111
2104
  `;
2112
2105
  }
2106
+ function createTw(prefix) {
2107
+ return (classList)=>classList.split(/\s+/u).filter(Boolean).map((candidate)=>`${prefix}:${candidate.replace(/\[&&\]:/gu, '')}`).join(' ');
2108
+ }
2113
2109
  function createCommerceAssetSvg(title, palette) {
2114
2110
  return `<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900" viewBox="0 0 1440 900" role="img" aria-label="${title}">
2115
2111
  <defs>
@@ -2185,7 +2181,7 @@ function commerceAssetsForApp(app) {
2185
2181
  tractor: '#914d76'
2186
2182
  })
2187
2183
  };
2188
- if ('remote-explore' === app.id) return {
2184
+ if ('explore' === app.id) return {
2189
2185
  [commerceAssetPublicPath('autonomy.svg')]: createCommerceAssetSvg('Autonomous tractor concept', {
2190
2186
  accent: '#c26a2e',
2191
2187
  ground: '#668f55',
@@ -2211,7 +2207,7 @@ function commerceAssetsForApp(app) {
2211
2207
  tractor: '#914d76'
2212
2208
  })
2213
2209
  };
2214
- if ('remote-decide' === app.id) return {
2210
+ if ('decide' === app.id) return {
2215
2211
  [commerceAssetPublicPath('field-loader.svg')]: createCommerceAssetSvg('Field Loader 112 tractor detail', {
2216
2212
  accent: '#d6b85d',
2217
2213
  ground: '#84ad58',
@@ -2372,11 +2368,12 @@ const LocalizedHead = () => {
2372
2368
  `;
2373
2369
  }
2374
2370
  function createShellPage() {
2371
+ const tw = createTw(tailwindPrefixForApp(shellApp));
2375
2372
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2376
2373
  import { Helmet } from '@modern-js/runtime/head';
2377
2374
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2378
2375
  import ShellFrame from '../shell-frame';
2379
- import { StorePicker } from '../remote-components';
2376
+ import { StorePicker } from '../vertical-components';
2380
2377
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2381
2378
  import { ultramodernUiMarker } from '../../ultramodern-build';
2382
2379
 
@@ -2390,25 +2387,25 @@ export default function ShellHome() {
2390
2387
  return (
2391
2388
  <ShellFrame>
2392
2389
  <LocalizedHead />
2393
- <section className="mx-auto grid max-w-7xl items-center gap-8 py-8 md:grid-cols-[0.9fr_1.1fr] lg:gap-14">
2394
- <div className="min-w-0">
2395
- <p className="text-xs font-black uppercase tracking-[0.18em] text-emerald-800">{t('shell.hero.eyebrow')}</p>
2396
- <h1 className="mt-3 max-w-3xl text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">{t('shell.title')}</h1>
2397
- <p className="mt-5 max-w-2xl text-lg leading-8 text-stone-600">{t('shell.hero.lede')}</p>
2398
- <div className="mt-7 flex flex-wrap gap-3">
2399
- <a className="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\`}>
2390
+ <section className="${tw('mx-auto grid max-w-7xl items-center gap-8 py-8 md:grid-cols-[0.9fr_1.1fr] lg:gap-14')}">
2391
+ <div className="${tw('min-w-0')}">
2392
+ <p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('shell.hero.eyebrow')}</p>
2393
+ <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
+ <p className="${tw('mt-5 max-w-2xl text-lg leading-8 text-stone-600')}">{t('shell.hero.lede')}</p>
2395
+ <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\`}>
2400
2397
  {t('shell.hero.primary')}
2401
2398
  </a>
2402
- <a className="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\`}>
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\`}>
2403
2400
  {t('shell.hero.secondary')}
2404
2401
  </a>
2405
2402
  </div>
2406
2403
  </div>
2407
- <img alt="" className="aspect-[16/10] w-full rounded-3xl bg-stone-200 object-cover shadow-2xl shadow-stone-900/20" src={heroField} />
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} />
2408
2405
  </section>
2409
2406
  <StorePicker />
2410
- <p className="sr-only" data-testid="ultramodern-preset">presetUltramodern workspace</p>
2411
- <p className="sr-only" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2407
+ <p className="${tw('sr-only')}" data-testid="ultramodern-preset">presetUltramodern workspace</p>
2408
+ <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2412
2409
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2413
2410
  </p>
2414
2411
  </ShellFrame>
@@ -2420,7 +2417,7 @@ function createShellTractorsPage() {
2420
2417
  return `import { Helmet } from '@modern-js/runtime/head';
2421
2418
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2422
2419
  import ShellFrame from '../../shell-frame';
2423
- import { Recommendations } from '../../remote-components';
2420
+ import { Recommendations } from '../../vertical-components';
2424
2421
  import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2425
2422
 
2426
2423
  ${createLocalizedHeadComponent()}
@@ -2434,17 +2431,35 @@ export default function ShellTractorsPage() {
2434
2431
  }
2435
2432
  `;
2436
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
+ }
2437
2452
  function createShellProductPage() {
2438
2453
  return `import { Helmet } from '@modern-js/runtime/head';
2439
2454
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2440
2455
  import ShellFrame from '../../../shell-frame';
2441
- import { ProductPage } from '../../../remote-components';
2456
+ import { ProductPage } from '../../../vertical-components';
2442
2457
  import { ultramodernLocalisedUrls } from '../../../ultramodern-route-metadata';
2443
2458
 
2444
2459
  ${createLocalizedHeadComponent()}
2445
2460
  export default function ShellProductPage() {
2446
2461
  return (
2447
- <ShellFrame>
2462
+ <ShellFrame boundary="decide">
2448
2463
  <LocalizedHead />
2449
2464
  <ProductPage />
2450
2465
  </ShellFrame>
@@ -2456,13 +2471,13 @@ function createShellCartPage() {
2456
2471
  return `import { Helmet } from '@modern-js/runtime/head';
2457
2472
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2458
2473
  import ShellFrame from '../../shell-frame';
2459
- import { CartPage } from '../../remote-components';
2474
+ import { CartPage } from '../../vertical-components';
2460
2475
  import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2461
2476
 
2462
2477
  ${createLocalizedHeadComponent()}
2463
2478
  export default function ShellCartPage() {
2464
2479
  return (
2465
- <ShellFrame showCart={false}>
2480
+ <ShellFrame boundary="checkout" showCart={false}>
2466
2481
  <LocalizedHead />
2467
2482
  <CartPage />
2468
2483
  </ShellFrame>
@@ -2471,17 +2486,19 @@ export default function ShellCartPage() {
2471
2486
  `;
2472
2487
  }
2473
2488
  function createShellFrameComponent() {
2489
+ const tw = createTw(tailwindPrefixForApp(shellApp));
2474
2490
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2475
2491
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2476
2492
  import type { ReactNode } from 'react';
2477
2493
  import BoundaryOverlay from './boundary-overlay';
2478
- import { Header, MiniCart } from './remote-components';
2494
+ import { Header, MiniCart } from './vertical-components';
2479
2495
  import { ultramodernLocalisedUrls } from './ultramodern-route-metadata';
2480
2496
 
2481
2497
  const supportedLanguages = ['en', 'cs'] as const;
2482
2498
  type SupportedLanguage = (typeof supportedLanguages)[number];
2483
2499
 
2484
2500
  type ShellFrameProps = {
2501
+ boundary?: 'checkout' | 'decide' | 'explore';
2485
2502
  children: ReactNode;
2486
2503
  showCart?: boolean;
2487
2504
  };
@@ -2601,24 +2618,25 @@ const locationSuffix = (location: {
2601
2618
  return \`\${locationSearch}\${locationHash}\`;
2602
2619
  };
2603
2620
 
2604
- export default function ShellFrame({ children, showCart = true }: ShellFrameProps) {
2621
+ export default function ShellFrame({ boundary = 'explore', children, showCart = true }: ShellFrameProps) {
2605
2622
  const { i18nInstance, language } = useModernI18n();
2606
2623
  const t = i18nInstance['t'].bind(i18nInstance);
2607
2624
  const location = useLocation();
2608
2625
  const suffix = locationSuffix(location);
2609
2626
 
2610
2627
  return (
2611
- <main className="min-h-screen bg-um-canvas px-4 py-5 text-um-foreground sm:px-6 lg:px-12">
2612
- <BoundaryOverlay />
2613
- <div className="mx-auto flex min-h-20 max-w-7xl flex-wrap items-center justify-between gap-3 bg-white/90 px-4 py-3 shadow-xl shadow-stone-900/10 sm:px-6">
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}>
2629
+ <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')}">
2614
2630
  <Header />
2615
- <div className="ml-auto flex min-w-0 items-center gap-2">
2616
- <label className="sr-only" htmlFor="ultramodern-language">
2631
+ <div className="${tw('flex min-w-0 flex-wrap items-center gap-2 md:ml-auto')}">
2632
+ <label className="${tw('sr-only')}" htmlFor="ultramodern-language">
2617
2633
  {t('shell.language.switcher')}
2618
2634
  </label>
2619
2635
  <select
2620
- className="h-10 rounded-full border border-stone-900/15 bg-white px-3 text-sm font-extrabold text-stone-950 shadow-lg shadow-stone-900/5"
2636
+ aria-label={t('shell.language.switcher')}
2637
+ className="${tw('h-10 w-10 cursor-pointer appearance-none border-0 bg-transparent p-0 text-center text-3xl font-black leading-none text-stone-950 shadow-none [appearance:none] [text-align-last:center] focus-visible:rounded-md focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-emerald-700/40 [&::-ms-expand]:hidden [&::picker-icon]:hidden [&_option]:text-xl')}"
2621
2638
  id="ultramodern-language"
2639
+ name="language"
2622
2640
  onChange={event => {
2623
2641
  const nextLanguage = event.currentTarget.value;
2624
2642
  if (isSupportedLanguage(nextLanguage)) {
@@ -2629,15 +2647,17 @@ export default function ShellFrame({ children, showCart = true }: ShellFrameProp
2629
2647
  }}
2630
2648
  value={language}
2631
2649
  >
2632
- {supportedLanguages.map(code => (
2633
- <option key={code} value={code}>
2634
- {t(\`shell.language.\${code}\`)}
2635
- </option>
2636
- ))}
2650
+ <option aria-label={t('shell.language.en')} value="en">
2651
+ 🇬🇧
2652
+ </option>
2653
+ <option aria-label={t('shell.language.cs')} value="cs">
2654
+ 🇨🇿
2655
+ </option>
2637
2656
  </select>
2638
2657
  {showCart ? <MiniCart /> : null}
2639
2658
  </div>
2640
2659
  </div>
2660
+ <BoundaryOverlay />
2641
2661
  {children}
2642
2662
  </main>
2643
2663
  );
@@ -2645,6 +2665,7 @@ export default function ShellFrame({ children, showCart = true }: ShellFrameProp
2645
2665
  `;
2646
2666
  }
2647
2667
  function createShellBoundaryOverlay() {
2668
+ const tw = createTw(tailwindPrefixForApp(shellApp));
2648
2669
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2649
2670
  import { useEffect, useMemo, useState, type CSSProperties } from 'react';
2650
2671
 
@@ -2656,7 +2677,7 @@ type BoundaryConfig = {
2656
2677
  type BoundaryBox = BoundaryConfig & {
2657
2678
  height: number;
2658
2679
  id: string;
2659
- labelPlacement: 'above' | 'inside';
2680
+ labelPlacement: 'above' | 'edge' | 'inside';
2660
2681
  left: number;
2661
2682
  top: number;
2662
2683
  width: number;
@@ -2678,6 +2699,7 @@ const boundaryIds = ['explore', 'decide', 'checkout'] as const;
2678
2699
 
2679
2700
  export default function BoundaryOverlay() {
2680
2701
  const { i18nInstance, language } = useModernI18n();
2702
+ const [mounted, setMounted] = useState(false);
2681
2703
  const [enabled, setEnabled] = useState(false);
2682
2704
  const [boxes, setBoxes] = useState<BoundaryBox[]>([]);
2683
2705
  const boundaryConfig = useMemo(() => {
@@ -2701,6 +2723,10 @@ export default function BoundaryOverlay() {
2701
2723
  'shell.boundaries.toggle',
2702
2724
  );
2703
2725
 
2726
+ useEffect(() => {
2727
+ setMounted(true);
2728
+ }, []);
2729
+
2704
2730
  useEffect(() => {
2705
2731
  if (!enabled) {
2706
2732
  setBoxes([]);
@@ -2709,10 +2735,13 @@ export default function BoundaryOverlay() {
2709
2735
 
2710
2736
  const readBoxes = () => {
2711
2737
  const nextBoxes = Array.from(
2712
- document.querySelectorAll<HTMLElement>('[data-mf-boundary]'),
2738
+ document.querySelectorAll<HTMLElement>(
2739
+ '[data-mf-page-boundary], [data-mf-boundary]',
2740
+ ),
2713
2741
  )
2714
2742
  .map((element, index) => {
2715
- const id = element.dataset.mfBoundary ?? 'unknown';
2743
+ const pageBoundary = element.dataset.mfPageBoundary;
2744
+ const id = pageBoundary ?? element.dataset.mfBoundary ?? 'unknown';
2716
2745
  const rect = element.getBoundingClientRect();
2717
2746
  if (rect.width <= 0 || rect.height <= 0) {
2718
2747
  return undefined;
@@ -2727,7 +2756,7 @@ export default function BoundaryOverlay() {
2727
2756
  ...config,
2728
2757
  height: rect.height,
2729
2758
  id: \`\${id}-\${index}\`,
2730
- labelPlacement: rect.height < 48 ? 'above' : 'inside',
2759
+ labelPlacement: pageBoundary ? 'edge' : rect.height < 48 ? 'above' : 'inside',
2731
2760
  left: rect.left,
2732
2761
  top: rect.top,
2733
2762
  width: rect.width,
@@ -2742,7 +2771,7 @@ export default function BoundaryOverlay() {
2742
2771
 
2743
2772
  const resizeObserver = new ResizeObserver(readBoxes);
2744
2773
  for (const element of document.querySelectorAll<HTMLElement>(
2745
- '[data-mf-boundary]',
2774
+ '[data-mf-page-boundary], [data-mf-boundary]',
2746
2775
  )) {
2747
2776
  resizeObserver.observe(element);
2748
2777
  }
@@ -2764,11 +2793,15 @@ export default function BoundaryOverlay() {
2764
2793
  };
2765
2794
  }, [boundaryConfig, enabled]);
2766
2795
 
2796
+ if (!mounted) {
2797
+ return null;
2798
+ }
2799
+
2767
2800
  return (
2768
2801
  <>
2769
- <label className="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">
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')}">
2770
2803
  <input
2771
- className="size-4 accent-emerald-800"
2804
+ className="${tw('size-4 accent-emerald-800')}"
2772
2805
  checked={enabled}
2773
2806
  onChange={event => setEnabled(event.currentTarget.checked)}
2774
2807
  type="checkbox"
@@ -2776,10 +2809,10 @@ export default function BoundaryOverlay() {
2776
2809
  <span>{toggleLabel}</span>
2777
2810
  </label>
2778
2811
  {enabled ? (
2779
- <div aria-hidden="true" className="pointer-events-none fixed inset-0 z-[70]">
2812
+ <div aria-hidden="true" className="${tw('pointer-events-none fixed inset-0 z-[70]')}">
2780
2813
  {boxes.map(box => (
2781
2814
  <div
2782
- className="fixed rounded-lg border"
2815
+ className="${tw('fixed rounded-lg border-2')}"
2783
2816
  data-label-placement={box.labelPlacement}
2784
2817
  key={box.id}
2785
2818
  style={
@@ -2794,7 +2827,7 @@ export default function BoundaryOverlay() {
2794
2827
  }
2795
2828
  >
2796
2829
  <span
2797
- className={\`absolute right-1 top-1 whitespace-nowrap rounded-full px-2 py-1 text-[0.7rem] font-black leading-none text-stone-950 \${box.labelPlacement === 'above' ? 'bottom-[calc(100%+0.25rem)] top-auto' : ''}\`}
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')}'}\`}
2798
2831
  style={{ backgroundColor: box.color }}
2799
2832
  >
2800
2833
  {box.label}
@@ -2809,15 +2842,16 @@ export default function BoundaryOverlay() {
2809
2842
  `;
2810
2843
  }
2811
2844
  function createShellRemoteComponents(scope) {
2845
+ const tw = createTw(tailwindPrefixForApp(shellApp));
2812
2846
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
2813
2847
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
2814
2848
  import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
2815
- import HeaderServer from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/Header';
2816
- import StorePickerServer from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/StorePicker';
2817
- import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/Recommendations';
2818
- import ProductPageServer from '${ultramodern_workspace_packageName(scope, 'remote-decide')}/ProductPage';
2819
- import MiniCartServer from '${ultramodern_workspace_packageName(scope, 'remote-checkout')}/MiniCart';
2820
- import CartPageServer from '${ultramodern_workspace_packageName(scope, 'remote-checkout')}/CartPage';
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';
2821
2855
 
2822
2856
  type RemoteComponentModule = {
2823
2857
  default: ComponentType;
@@ -2833,7 +2867,7 @@ const loadRemoteComponent = async (specifier: string) => {
2833
2867
 
2834
2868
  const remoteFallback =
2835
2869
  ({ error }: { error: Error }) =>
2836
- <div className="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}>Remote unavailable</div>;
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>;
2837
2871
 
2838
2872
  const createHydratedRemote = (
2839
2873
  ServerComponent: ComponentType,
@@ -2884,6 +2918,7 @@ export const CartPage = createHydratedRemote(CartPageServer, 'checkout/CartPage'
2884
2918
  `;
2885
2919
  }
2886
2920
  function createRemotePage(app) {
2921
+ const tw = createTw(tailwindPrefixForApp(app));
2887
2922
  const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2888
2923
  import { Helmet } from '@modern-js/runtime/head';
2889
2924
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
@@ -2925,13 +2960,13 @@ export default function ${toPascalCase(app.id)}Home() {
2925
2960
  const location = useLocation();
2926
2961
  const suffix = locationSuffix(location);
2927
2962
  ${effectBffState} return (
2928
- <main className="min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8">
2963
+ <main className="${tw('min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8')}">
2929
2964
  <LocalizedHead />
2930
- <nav aria-label={t('${app.domain}.language.switcher')} className="flex gap-3">
2965
+ <nav aria-label={t('${app.domain}.language.switcher')} className="${tw('flex gap-3')}">
2931
2966
  {supportedLanguages.map(code => (
2932
2967
  <a
2933
2968
  aria-current={language === code ? 'page' : undefined}
2934
- className="rounded-full border border-stone-900/15 bg-white px-4 py-2 text-sm font-bold text-stone-950 no-underline"
2969
+ className="${tw('rounded-full border border-stone-900/15 bg-white px-4 py-2 text-sm font-bold text-stone-950 no-underline')}"
2935
2970
  href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2936
2971
  key={code}
2937
2972
  >
@@ -2939,9 +2974,9 @@ ${effectBffState} return (
2939
2974
  </a>
2940
2975
  ))}
2941
2976
  </nav>
2942
- <h1 className="mt-10 text-5xl font-black">{t('${app.domain}.title')}</h1>
2943
- <p className="mt-3 text-lg text-stone-600" data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
2944
- <p className="sr-only" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2977
+ <h1 className="${tw('mt-10 text-5xl font-black')}">{t('${app.domain}.title')}</h1>
2978
+ <p className="${tw('mt-3 text-lg text-stone-600')}" data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
2979
+ <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2945
2980
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2946
2981
  </p>
2947
2982
  ${effectBffMarkup} </main>
@@ -2963,52 +2998,55 @@ export default function Layout() {
2963
2998
  `;
2964
2999
  }
2965
3000
  function createRemoteEntry(app) {
3001
+ const tw = createTw(tailwindPrefixForApp(app));
2966
3002
  if (app.exposes?.['./ProductPage']) return `export { default } from './components/product-page';
2967
3003
  `;
2968
3004
  if (app.exposes?.['./CartPage']) return `export { default } from './components/cart-page';
2969
3005
  `;
2970
3006
  return `export default function ${toPascalCase(app.domain ?? app.id)}Route() {
2971
3007
  return (
2972
- <section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}" data-mf-expose="./Route">
2973
- <h2 className="text-2xl font-black">${app.displayName}</h2>
2974
- <p className="mt-2 text-stone-600">Route surface for ${app.domain ?? app.id}.</p>
3008
+ <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>
2975
3011
  </section>
2976
3012
  );
2977
3013
  }
2978
3014
  `;
2979
3015
  }
2980
3016
  function createRemoteWidget(app) {
3017
+ const tw = createTw(tailwindPrefixForApp(app));
2981
3018
  const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
2982
3019
  const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
2983
3020
  return `export default function ${componentName}() {
2984
3021
  return (
2985
- <section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}">
2986
- <h2 className="text-2xl font-black">${app.displayName}</h2>
2987
- <p className="mt-2 text-stone-600">${body}</p>
3022
+ <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>
2988
3025
  </section>
2989
3026
  );
2990
3027
  }
2991
3028
  `;
2992
3029
  }
2993
3030
  function createRemoteExposeComponent(app, expose) {
2994
- if ('remote-explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3031
+ const tw = createTw(tailwindPrefixForApp(app));
3032
+ if ('explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2995
3033
 
2996
3034
  export default function Header() {
2997
3035
  const { i18nInstance, language } = useModernI18n();
2998
3036
  const t = i18nInstance['t'].bind(i18nInstance);
2999
3037
 
3000
3038
  return (
3001
- <header className="flex min-w-0 flex-1 flex-wrap items-center gap-x-8 gap-y-2" data-mf-boundary="explore">
3002
- <a className="whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline" href={\`/\${language}\`}>Acre & Iron</a>
3003
- <nav aria-label={t('explore.header.navigation')} className="flex items-center gap-5">
3004
- <a className="text-sm font-extrabold text-stone-900 no-underline" href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
3005
- <a className="text-sm font-extrabold text-stone-900 no-underline" href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
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>
3006
3044
  </nav>
3007
3045
  </header>
3008
3046
  );
3009
3047
  }
3010
3048
  `;
3011
- if ('remote-explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3049
+ if ('explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3012
3050
 
3013
3051
  const tractors = [
3014
3052
  { badge: 'explore.recommendations.bestRows', image: '${commerceAssetUrl('orchard.svg')}', name: 'Orchard Tractor', slug: 'orchard-tractor' },
@@ -3022,14 +3060,14 @@ export default function Recommendations() {
3022
3060
  const t = i18nInstance['t'].bind(i18nInstance);
3023
3061
 
3024
3062
  return (
3025
- <section className="mx-auto mt-12 max-w-7xl" data-mf-boundary="explore">
3026
- <h2 className="text-3xl font-black tracking-normal text-stone-950">{t('explore.recommendations.title')}</h2>
3027
- <div className="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-4">
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')}">
3028
3066
  {tractors.map(tractor => (
3029
- <a className="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}>
3030
- <img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={tractor.image} />
3031
- <span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-amber-700">{t(tractor.badge)}</span>
3032
- <strong className="mt-2 block text-xl font-black leading-tight">{tractor.name}</strong>
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>
3033
3071
  </a>
3034
3072
  ))}
3035
3073
  </div>
@@ -3037,7 +3075,7 @@ export default function Recommendations() {
3037
3075
  );
3038
3076
  }
3039
3077
  `;
3040
- if ('remote-explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3078
+ if ('explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3041
3079
 
3042
3080
  const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
3043
3081
  const vineyardImage = '${commerceAssetUrl('vineyard.svg')}';
@@ -3047,32 +3085,32 @@ export default function StorePicker() {
3047
3085
  const t = i18nInstance['t'].bind(i18nInstance);
3048
3086
 
3049
3087
  return (
3050
- <section className="mx-auto mt-12 max-w-7xl" data-mf-boundary="explore">
3051
- <h2 className="text-3xl font-black tracking-normal text-stone-950">{t('explore.stores.title')}</h2>
3052
- <div className="mt-5 grid gap-4 md:grid-cols-2">
3053
- <article className="rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10">
3054
- <img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={fieldLoaderImage} />
3055
- <span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800">{t('explore.stores.northRegion')}</span>
3056
- <strong className="mt-2 block text-2xl font-black">Bohemia Field Supply</strong>
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>
3090
+ <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>
3057
3095
  </article>
3058
- <article className="rounded-2xl bg-white/90 p-4 shadow-xl shadow-stone-900/10">
3059
- <img alt="" className="aspect-video w-full rounded-xl bg-stone-200 object-cover" src={vineyardImage} />
3060
- <span className="mt-4 block text-xs font-black uppercase tracking-[0.16em] text-emerald-800">{t('explore.stores.southRegion')}</span>
3061
- <strong className="mt-2 block text-2xl font-black">Moravia Iron Works</strong>
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>
3062
3100
  </article>
3063
3101
  </div>
3064
3102
  </section>
3065
3103
  );
3066
3104
  }
3067
3105
  `;
3068
- if ('remote-explore' === app.id && './Footer' === expose) return `export default function Footer() {
3069
- return <footer className="mx-auto mt-12 max-w-7xl text-sm font-bold text-stone-600" data-mf-boundary="explore">Acre & Iron</footer>;
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>;
3070
3108
  }
3071
3109
  `;
3072
3110
  if ('./Widget' === expose) return createRemoteWidget(app);
3073
3111
  const componentName = `${toPascalCase(app.domain ?? app.id)}${toPascalCase(expose.replace(/^\.\//u, ''))}`;
3074
- if ('remote-decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3075
- import { AddToCart, Recommendations } from './remote-components';
3112
+ if ('decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3113
+ import { AddToCart, Recommendations } from './vertical-components';
3076
3114
 
3077
3115
  const fieldLoaderImage = '${commerceAssetUrl('field-loader.svg')}';
3078
3116
 
@@ -3082,16 +3120,16 @@ export default function ${componentName}() {
3082
3120
 
3083
3121
  return (
3084
3122
  <>
3085
- <section className="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}">
3086
- <img alt="" className="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} />
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} />
3087
3125
  <div>
3088
- <p className="text-xs font-black uppercase tracking-[0.18em] text-emerald-800">{t('decide.product.eyebrow')}</p>
3089
- <h1 className="mt-3 text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">Field Loader 112</h1>
3090
- <p className="mt-5 max-w-2xl text-lg leading-8 text-stone-600">{t('decide.product.lede')}</p>
3091
- <div className="mt-8 grid gap-4 sm:grid-cols-3">
3092
- <article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.price')}</span><strong className="mt-2 block text-lg font-black">EUR 42,500</strong></article>
3093
- <article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.power')}</span><strong className="mt-2 block text-lg font-black">112 hp</strong></article>
3094
- <article className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10"><span className="block text-sm font-bold text-stone-500">{t('decide.product.availability')}</span><strong className="mt-2 block text-lg font-black">{t('decide.product.inStock')}</strong></article>
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>
3129
+ <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>
3095
3133
  </div>
3096
3134
  <AddToCart />
3097
3135
  </div>
@@ -3101,7 +3139,7 @@ export default function ${componentName}() {
3101
3139
  );
3102
3140
  }
3103
3141
  `;
3104
- if ('remote-checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3142
+ if ('checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3105
3143
  import { useCartLines } from '../cart-store';
3106
3144
 
3107
3145
  export default function ${componentName}() {
@@ -3110,18 +3148,18 @@ export default function ${componentName}() {
3110
3148
  const cart = useCartLines();
3111
3149
 
3112
3150
  return (
3113
- <div className="mt-8 flex flex-wrap gap-3" data-mf-boundary="checkout">
3114
- <button className="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">
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">
3115
3153
  {t('checkout.actions.addToCart')}
3116
3154
  </button>
3117
- <a className="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\`}>
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\`}>
3118
3156
  {t('checkout.actions.viewCart')}
3119
3157
  </a>
3120
3158
  </div>
3121
3159
  );
3122
3160
  }
3123
3161
  `;
3124
- if ('remote-checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3162
+ if ('checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3125
3163
  import { useCartLines } from '../cart-store';
3126
3164
 
3127
3165
  export default function ${componentName}() {
@@ -3131,13 +3169,13 @@ export default function ${componentName}() {
3131
3169
  const count = cart.lines.reduce((sum, line) => sum + line.quantity, 0);
3132
3170
 
3133
3171
  return (
3134
- <a className="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\`}>
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\`}>
3135
3173
  {t('checkout.cart.title')} ({count})
3136
3174
  </a>
3137
3175
  );
3138
3176
  }
3139
3177
  `;
3140
- if ('remote-checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3178
+ if ('checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3141
3179
  import { useCartLines } from '../cart-store';
3142
3180
 
3143
3181
  export default function ${componentName}() {
@@ -3146,24 +3184,24 @@ export default function ${componentName}() {
3146
3184
  const cart = useCartLines();
3147
3185
 
3148
3186
  return (
3149
- <section className="mx-auto mt-10 max-w-7xl" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3150
- <h1 className="text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl">{t('checkout.cart.title')}</h1>
3151
- <div className="mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10">
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>
3189
+ <div className="${tw('mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
3152
3190
  {cart.lines.length === 0 ? (
3153
3191
  <p>{t('checkout.cart.empty')}</p>
3154
3192
  ) : (
3155
3193
  <>
3156
3194
  {cart.lines.map(line => (
3157
- <article className="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}>
3195
+ <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}>
3158
3196
  <div>
3159
- <strong className="text-lg font-black">{line.name}</strong>
3160
- <p className="text-stone-600">EUR {line.price.toLocaleString('en-US')}</p>
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>
3161
3199
  </div>
3162
- <div className="flex flex-wrap items-center gap-2">
3163
- <button className="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>
3164
- <span className="min-w-6 text-center font-black">{line.quantity}</span>
3165
- <button className="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>
3166
- <button className="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">
3200
+ <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">
3167
3205
  {t('checkout.actions.remove')}
3168
3206
  </button>
3169
3207
  </div>
@@ -3179,20 +3217,21 @@ export default function ${componentName}() {
3179
3217
  `;
3180
3218
  return `export default function ${componentName}() {
3181
3219
  return (
3182
- <section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3183
- <h2 className="text-2xl font-black">${app.displayName} ${expose.replace(/^\.\//u, '')}</h2>
3184
- <p className="mt-2 text-stone-600">Module Federation surface owned by ${app.ownership.team}.</p>
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>
3185
3223
  </section>
3186
3224
  );
3187
3225
  }
3188
3226
  `;
3189
3227
  }
3190
- function createDecideRemoteComponents(scope) {
3228
+ function createDecideRemoteComponents(scope, app) {
3229
+ const tw = createTw(tailwindPrefixForApp(app));
3191
3230
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
3192
3231
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
3193
3232
  import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
3194
- import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/Recommendations';
3195
- import AddToCartServer from '${ultramodern_workspace_packageName(scope, 'remote-checkout')}/AddToCart';
3233
+ import RecommendationsServer from '${ultramodern_workspace_packageName(scope, 'explore')}/Recommendations';
3234
+ import AddToCartServer from '${ultramodern_workspace_packageName(scope, 'checkout')}/AddToCart';
3196
3235
 
3197
3236
  type RemoteComponentModule = {
3198
3237
  default: ComponentType;
@@ -3208,7 +3247,7 @@ const loadRemoteComponent = async (specifier: string) => {
3208
3247
 
3209
3248
  const remoteFallback =
3210
3249
  ({ error }: { error: Error }) =>
3211
- <div className="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}>Remote unavailable</div>;
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>;
3212
3251
 
3213
3252
  const createHydratedRemote = (
3214
3253
  ServerComponent: ComponentType,
@@ -3296,9 +3335,9 @@ function createAppLocaleMessages(app, language) {
3296
3335
  switcher: 'en' === language ? 'Language' : 'Jazyk'
3297
3336
  },
3298
3337
  remotes: {
3299
- checkout: 'en' === language ? 'Checkout Remote' : 'Checkout remote',
3300
- decide: 'en' === language ? 'Decide Remote' : 'Decide remote',
3301
- explore: 'en' === language ? 'Explore Remote' : 'Explore remote'
3338
+ checkout: 'en' === language ? 'Checkout Vertical' : 'Checkout remote',
3339
+ decide: 'en' === language ? 'Decide Vertical' : 'Decide remote',
3340
+ explore: 'en' === language ? 'Explore Vertical' : 'Explore remote'
3302
3341
  },
3303
3342
  boundaries: {
3304
3343
  checkout: 'en' === language ? 'checkout' : 'pokladna',
@@ -3310,7 +3349,8 @@ function createAppLocaleMessages(app, language) {
3310
3349
  cart: 'en' === language ? 'Cart' : 'Košík',
3311
3350
  home: 'en' === language ? 'Home' : 'Domů',
3312
3351
  listing: 'en' === language ? 'Tractors' : 'Traktory',
3313
- productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru'
3352
+ productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru',
3353
+ storePicker: 'en' === language ? 'Stores' : 'Prodejci'
3314
3354
  },
3315
3355
  title: 'en' === language ? 'Acre & Iron' : 'Acre & Iron'
3316
3356
  }
@@ -3383,10 +3423,11 @@ function createAppLocaleMessages(app, language) {
3383
3423
  }
3384
3424
  };
3385
3425
  }
3386
- function createDesignButton() {
3426
+ function createDesignButton(app) {
3427
+ const tw = createTw(tailwindPrefixForApp(app));
3387
3428
  return `export default function Button({ label }: { label: string }) {
3388
3429
  return (
3389
- <button className="rounded-full text-um-foreground" type="button">
3430
+ <button className="${tw('rounded-full text-um-foreground')}" type="button">
3390
3431
  {label}
3391
3432
  </button>
3392
3433
  );
@@ -3511,35 +3552,35 @@ function createSharedDesignTokensCss() {
3511
3552
  }
3512
3553
  `;
3513
3554
  }
3514
- function serviceEffectApiExport(service = effectService) {
3555
+ function serviceEffectApiExport(service) {
3515
3556
  return `${toCamelCase(effectApiStem(service))}EffectApi`;
3516
3557
  }
3517
- function serviceEffectGroupName(service = effectService) {
3558
+ function serviceEffectGroupName(service) {
3518
3559
  return toCamelCase(effectApiStem(service));
3519
3560
  }
3520
- function serviceEffectApiName(service = effectService) {
3561
+ function serviceEffectApiName(service) {
3521
3562
  return `${toPascalCase(effectApiStem(service))}EffectApi`;
3522
3563
  }
3523
- function serviceEffectSchemaExport(service = effectService) {
3564
+ function serviceEffectSchemaExport(service) {
3524
3565
  return `${toCamelCase(effectApiStem(service))}ItemSchema`;
3525
3566
  }
3526
- function serviceEffectMarkerSchemaExport(service = effectService) {
3567
+ function serviceEffectMarkerSchemaExport(service) {
3527
3568
  return `${toCamelCase(effectApiStem(service))}MarkerSchema`;
3528
3569
  }
3529
- function serviceEffectReadinessSchemaExport(service = effectService) {
3570
+ function serviceEffectReadinessSchemaExport(service) {
3530
3571
  return `${toCamelCase(effectApiStem(service))}ReadinessSchema`;
3531
3572
  }
3532
- function serviceEffectErrorStem(service = effectService) {
3573
+ function serviceEffectErrorStem(service) {
3533
3574
  const stem = effectApiStem(service);
3534
3575
  return 'recommendations' === stem ? 'recommendation' : stem;
3535
3576
  }
3536
- function serviceEffectCreatePayloadSchemaExport(service = effectService) {
3577
+ function serviceEffectCreatePayloadSchemaExport(service) {
3537
3578
  return `${toCamelCase(effectApiStem(service))}CreatePayloadSchema`;
3538
3579
  }
3539
- function serviceEffectNotFoundErrorExport(service = effectService) {
3580
+ function serviceEffectNotFoundErrorExport(service) {
3540
3581
  return `${toPascalCase(serviceEffectErrorStem(service))}NotFound`;
3541
3582
  }
3542
- function serviceEffectNotFoundSchemaExport(service = effectService) {
3583
+ function serviceEffectNotFoundSchemaExport(service) {
3543
3584
  return `${toCamelCase(serviceEffectErrorStem(service))}NotFoundSchema`;
3544
3585
  }
3545
3586
  function createEffectSharedApiImports() {
@@ -3552,7 +3593,7 @@ function createEffectSharedApiImports() {
3552
3593
  } from '@modern-js/plugin-bff/effect-client';
3553
3594
  `;
3554
3595
  }
3555
- function createEffectSharedApiContract(service = effectService) {
3596
+ function createEffectSharedApiContract(service) {
3556
3597
  const schemaExport = serviceEffectSchemaExport(service);
3557
3598
  const markerSchemaExport = serviceEffectMarkerSchemaExport(service);
3558
3599
  const readinessSchemaExport = serviceEffectReadinessSchemaExport(service);
@@ -3693,7 +3734,7 @@ ${createEffectSharedApiContract(service)}`;
3693
3734
  } as const;
3694
3735
  `;
3695
3736
  }
3696
- function createEffectServiceEntry(scope, service = effectService, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
3737
+ function createEffectServiceEntry(scope, service, contractImportPath = ultramodern_workspace_packageName(scope, 'shared-effect-api')) {
3697
3738
  const apiExport = serviceEffectApiExport(service);
3698
3739
  const groupName = serviceEffectGroupName(service);
3699
3740
  const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
@@ -3917,7 +3958,7 @@ function createShellEffectClient(scope) {
3917
3958
  getCheckoutReadiness,
3918
3959
  listCheckout,
3919
3960
  type CheckoutClientOptions,
3920
- } from '${ultramodern_workspace_packageName(scope, 'remote-checkout')}/effect/client';
3961
+ } from '${ultramodern_workspace_packageName(scope, 'checkout')}/effect/client';
3921
3962
 
3922
3963
  export {
3923
3964
  createDecide,
@@ -3926,7 +3967,7 @@ export {
3926
3967
  getDecideReadiness,
3927
3968
  listDecide,
3928
3969
  type DecideClientOptions,
3929
- } from '${ultramodern_workspace_packageName(scope, 'remote-decide')}/effect/client';
3970
+ } from '${ultramodern_workspace_packageName(scope, 'decide')}/effect/client';
3930
3971
 
3931
3972
  export {
3932
3973
  createExplore,
@@ -3935,7 +3976,7 @@ export {
3935
3976
  getExploreReadiness,
3936
3977
  listExplore,
3937
3978
  type ExploreClientOptions,
3938
- } from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/effect/client';
3979
+ } from '${ultramodern_workspace_packageName(scope, 'explore')}/effect/client';
3939
3980
  `;
3940
3981
  }
3941
3982
  function toPascalCase(value) {
@@ -4076,14 +4117,13 @@ function createTopology(scope) {
4076
4117
  return {
4077
4118
  schemaVersion: 1,
4078
4119
  id: 'ultramodern-superapp-workspace-reference-topology',
4079
- description: 'Generated UltraModern workspace skeleton based on the reference topology shape.',
4120
+ description: 'Generated UltraModern workspace skeleton with full-stack vertical ownership.',
4080
4121
  preset: 'presetUltramodern',
4081
- sourceFixture: "scripts/mv-integration-pilot/__fixtures__/reference-topology.json",
4082
4122
  shell: {
4083
4123
  id: shellApp.id,
4084
4124
  kind: 'shell',
4085
4125
  package: ultramodern_workspace_packageName(scope, shellApp.packageSuffix),
4086
- remoteRefs: shellApp.remoteRefs,
4126
+ verticalRefs: shellApp.verticalRefs,
4087
4127
  moduleFederation: {
4088
4128
  role: 'host',
4089
4129
  name: shellApp.mfName,
@@ -4094,31 +4134,31 @@ function createTopology(scope) {
4094
4134
  cloudflare: createCloudflareDeployContract(scope, shellApp),
4095
4135
  ownership: shellApp.ownership
4096
4136
  },
4097
- remotes: remoteApps.map((remote)=>({
4098
- id: remote.id,
4099
- kind: remote.kind,
4100
- domain: remote.domain,
4101
- package: ultramodern_workspace_packageName(scope, remote.packageSuffix),
4137
+ verticals: verticalApps.map((vertical)=>({
4138
+ id: vertical.id,
4139
+ kind: vertical.kind,
4140
+ domain: vertical.domain,
4141
+ package: ultramodern_workspace_packageName(scope, vertical.packageSuffix),
4142
+ path: vertical.directory,
4102
4143
  moduleFederation: {
4103
4144
  role: 'remote',
4104
- name: remote.mfName,
4105
- manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
4106
- exposes: Object.keys(remote.exposes ?? {}),
4107
- ...remote.remoteRefs?.length ? {
4108
- remoteRefs: remote.remoteRefs,
4109
- remotes: createModuleFederationRemoteContracts(remote)
4145
+ name: vertical.mfName,
4146
+ manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
4147
+ exposes: Object.keys(vertical.exposes ?? {}),
4148
+ ...vertical.verticalRefs?.length ? {
4149
+ verticalRefs: vertical.verticalRefs,
4150
+ remotes: createModuleFederationRemoteContracts(vertical)
4110
4151
  } : {},
4111
4152
  ssr: true,
4112
4153
  fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
4113
4154
  sharedContractVersion: 'mf-ssr-contract-v1'
4114
4155
  },
4115
- ...effectApiTopologyMetadata(remote) ? {
4116
- api: effectApiTopologyMetadata(remote)
4156
+ ...effectApiTopologyMetadata(vertical) ? {
4157
+ api: effectApiTopologyMetadata(vertical)
4117
4158
  } : {},
4118
- cloudflare: createCloudflareDeployContract(scope, remote),
4119
- ownership: remote.ownership
4159
+ cloudflare: createCloudflareDeployContract(scope, vertical),
4160
+ ownership: vertical.ownership
4120
4161
  })),
4121
- effectServices: [],
4122
4162
  sharedPackages: sharedPackages.map((sharedPackage)=>({
4123
4163
  id: sharedPackage.id,
4124
4164
  package: ultramodern_workspace_packageName(scope, sharedPackage.id),
@@ -4128,7 +4168,7 @@ function createTopology(scope) {
4128
4168
  validation: {
4129
4169
  script: "scripts/validate-ultramodern-workspace.mjs",
4130
4170
  commands: [
4131
- 'mise exec -- pnpm ultramodern:check'
4171
+ 'pnpm ultramodern:check'
4132
4172
  ]
4133
4173
  }
4134
4174
  };
@@ -4139,7 +4179,7 @@ function createOwnership(scope) {
4139
4179
  preset: 'presetUltramodern',
4140
4180
  owners: [
4141
4181
  shellApp,
4142
- ...remoteApps,
4182
+ ...verticalApps,
4143
4183
  ...sharedPackages.map((sharedPackage)=>({
4144
4184
  id: sharedPackage.id,
4145
4185
  packageSuffix: sharedPackage.id,
@@ -4173,12 +4213,12 @@ function createDevelopmentOverlay() {
4173
4213
  preset: 'presetUltramodern',
4174
4214
  ports: Object.fromEntries([
4175
4215
  shellApp,
4176
- ...remoteApps
4216
+ ...verticalApps
4177
4217
  ].map((app)=>[
4178
4218
  app.id,
4179
4219
  app.port
4180
4220
  ])),
4181
- manifests: Object.fromEntries(remoteApps.map((remote)=>[
4221
+ manifests: Object.fromEntries(verticalApps.map((remote)=>[
4182
4222
  remote.id,
4183
4223
  `http://localhost:${remote.port}/mf-manifest.json`
4184
4224
  ])),
@@ -4260,8 +4300,9 @@ function createAppConfigContract(app) {
4260
4300
  output: {
4261
4301
  assetPrefix: {
4262
4302
  envFallbackOrder: [
4263
- 'MODERN_PUBLIC_SITE_URL',
4264
4303
  createCloudflarePublicUrlEnv(app),
4304
+ 'MODERN_PUBLIC_SITE_URL',
4305
+ 'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
4265
4306
  app.portEnv
4266
4307
  ],
4267
4308
  defaultLocalhostPort: app.port
@@ -4297,15 +4338,14 @@ function createAppConfigContract(app) {
4297
4338
  }
4298
4339
  function cssLayerName(app) {
4299
4340
  if ('shell' === app.kind) return 'ultramodern-shell-base';
4300
- return `ultramodern-remote-${app.domain ?? app.id}`;
4341
+ return `ultramodern-vertical-${app.domain ?? app.id}`;
4301
4342
  }
4302
4343
  function cssRole(app) {
4303
4344
  if ('shell' === app.kind) return 'shell-base-overlay';
4304
- return 'horizontal-remote' === app.kind ? 'horizontal-remote-css' : 'vertical-remote-css';
4345
+ return 'vertical-css';
4305
4346
  }
4306
4347
  function cssClassPrefix(app) {
4307
- if ('shell' === app.kind) return 'shell-';
4308
- return `${app.domain ?? app.id.replace(/^remote-/, '')}-`;
4348
+ return `${tailwindPrefixForApp(app)}:`;
4309
4349
  }
4310
4350
  function createCssDedupeContract(scope) {
4311
4351
  return {
@@ -4323,7 +4363,7 @@ function createCssSsrContract(app) {
4323
4363
  cloudflare: true,
4324
4364
  firstPaintRequired: true,
4325
4365
  linkEmission: 'modern-ssr-css-assets',
4326
- remoteCss: 'shell' === app.kind ? 'host-preloads-shell-and-shared-css' : 'remote-manifest-owned-css'
4366
+ verticalCss: 'shell' === app.kind ? 'host-preloads-shell-and-shared-css' : 'federated-manifest-owned-css'
4327
4367
  };
4328
4368
  }
4329
4369
  function createAppCssFederationContract(scope, app) {
@@ -4359,7 +4399,7 @@ function createAppCssFederationContract(scope, app) {
4359
4399
  'src/routes/index.css'
4360
4400
  ],
4361
4401
  ...'shell' !== app.kind ? {
4362
- remoteEntry: 'src/remote-entry.tsx'
4402
+ federationEntry: 'src/federation-entry.tsx'
4363
4403
  } : {}
4364
4404
  },
4365
4405
  assets: {
@@ -4421,10 +4461,10 @@ function createCssFederationContract(scope) {
4421
4461
  'base',
4422
4462
  'overlay'
4423
4463
  ],
4424
- remotes: [
4464
+ verticals: [
4425
4465
  'vertical-css'
4426
4466
  ],
4427
- forbiddenRemoteLayers: [
4467
+ forbiddenVerticalLayers: [
4428
4468
  'ultramodern-shell-base',
4429
4469
  'ultramodern-shell-overlay'
4430
4470
  ]
@@ -4438,9 +4478,9 @@ function createStylingContract(scope, app, enableTailwind) {
4438
4478
  postcssPlugins: [
4439
4479
  '@tailwindcss/postcss'
4440
4480
  ],
4441
- contentGlobs: [
4442
- './src/**/*.{js,jsx,ts,tsx}'
4443
- ]
4481
+ prefix: tailwindPrefixForApp(app),
4482
+ source: '..',
4483
+ sourceMode: 'source(none)'
4444
4484
  } : {},
4445
4485
  federation: createAppCssFederationContract(scope, app)
4446
4486
  };
@@ -4448,7 +4488,7 @@ function createStylingContract(scope, app, enableTailwind) {
4448
4488
  function createAppGeneratedContract(scope, app, apps, enableTailwind) {
4449
4489
  const appWithResolvedRefs = 'shell' === app.kind ? {
4450
4490
  ...app,
4451
- remoteRefs: apps.filter((candidate)=>'shell' !== candidate.kind).map((candidate)=>candidate.id)
4491
+ verticalRefs: apps.filter((candidate)=>'shell' !== candidate.kind).map((candidate)=>candidate.id)
4452
4492
  } : app;
4453
4493
  const consumedRemotes = createModuleFederationRemoteContracts(appWithResolvedRefs, apps);
4454
4494
  return {
@@ -4512,8 +4552,8 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
4512
4552
  },
4513
4553
  moduleFederation: {
4514
4554
  name: app.mfName,
4515
- ...appWithResolvedRefs.remoteRefs?.length ? {
4516
- remoteRefs: appWithResolvedRefs.remoteRefs,
4555
+ ...appWithResolvedRefs.verticalRefs?.length ? {
4556
+ verticalRefs: appWithResolvedRefs.verticalRefs,
4517
4557
  remotes: consumedRemotes
4518
4558
  } : {},
4519
4559
  exposes: Object.keys(app.exposes ?? {}),
@@ -4554,7 +4594,7 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
4554
4594
  }
4555
4595
  function createGeneratedContract(scope, apps = [
4556
4596
  shellApp,
4557
- ...remoteApps
4597
+ ...verticalApps
4558
4598
  ], enableTailwind = true) {
4559
4599
  return {
4560
4600
  schemaVersion: 1,
@@ -4586,7 +4626,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4586
4626
  id: 'modernjs-ultramodern-superapp-workspace',
4587
4627
  version: modernVersion,
4588
4628
  displayName: 'Modern.js UltraModern SuperApp Workspace',
4589
- description: 'Canonical shell, remotes, Effect service, shared packages, and topology skeleton.',
4629
+ description: 'Canonical shell, full-stack verticals, shared packages, and topology skeleton.',
4590
4630
  compatibilityLane: 'ultramodern-mv',
4591
4631
  minimumModernVersion: modernVersion
4592
4632
  },
@@ -4614,6 +4654,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4614
4654
  targetRoot: 'generated-project-root',
4615
4655
  allowedPaths: [
4616
4656
  '.agents/**',
4657
+ '.codex/**',
4617
4658
  '.github/**',
4618
4659
  '.gitignore',
4619
4660
  '.mise.toml',
@@ -4622,6 +4663,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4622
4663
  'README.md',
4623
4664
  'apps/**',
4624
4665
  'packages/**',
4666
+ 'lefthook.yml',
4625
4667
  'package.json',
4626
4668
  'oxfmt.config.ts',
4627
4669
  'oxlint.config.ts',
@@ -4692,13 +4734,13 @@ function createTemplateManifest(modernVersion, packageSource) {
4692
4734
  ],
4693
4735
  expectedCommands: [
4694
4736
  'mise install',
4695
- 'mise exec -- pnpm install',
4696
- 'mise exec -- pnpm run ultramodern:check'
4737
+ 'pnpm install',
4738
+ 'pnpm run ultramodern:check'
4697
4739
  ]
4698
4740
  }
4699
4741
  };
4700
4742
  }
4701
- function createAssertMfTypesScript(remotes = remoteApps) {
4743
+ function createAssertMfTypesScript(remotes = verticalApps) {
4702
4744
  return `import fs from 'node:fs';
4703
4745
  import path from 'node:path';
4704
4746
 
@@ -4770,7 +4812,7 @@ for (const appDir of appDirs) {
4770
4812
  }
4771
4813
  `;
4772
4814
  }
4773
- function createWorkspaceValidationScript(scope, enableTailwind, remotes = remoteApps) {
4815
+ function createWorkspaceValidationScript(scope, enableTailwind, remotes = verticalApps) {
4774
4816
  const verticals = remotes.filter(appHasEffectApi).map((remote)=>({
4775
4817
  id: remote.id,
4776
4818
  domain: remote.domain,
@@ -4779,19 +4821,18 @@ function createWorkspaceValidationScript(scope, enableTailwind, remotes = remote
4779
4821
  path: remote.directory,
4780
4822
  mfName: remote.mfName,
4781
4823
  apiPrefix: remote.effectApi.prefix,
4824
+ tailwindPrefix: tailwindPrefixForApp(remote),
4782
4825
  packageName: ultramodern_workspace_packageName(scope, remote.packageSuffix),
4783
4826
  exposes: Object.keys(remote.exposes ?? {}),
4784
4827
  componentPaths: Object.keys(remote.exposes ?? {}).map((expose)=>remoteComponentOutputPath(remote, expose)).filter((componentPath)=>Boolean(componentPath)),
4785
4828
  namespace: appI18nNamespace(remote),
4786
4829
  routePagePaths: createRouteOwnedI18nPaths(remote).filter((route)=>'/' !== route.canonicalPath).map((route)=>createRoutePageFilePath(remote, route.canonicalPath)),
4787
4830
  localisedUrls: createLocalisedUrlsMap(remote),
4788
- remoteRefs: remote.remoteRefs ?? []
4831
+ verticalRefs: remote.verticalRefs ?? []
4789
4832
  }));
4790
4833
  const shellNamespace = appI18nNamespace(shellApp);
4791
4834
  const oldRemotePaths = [
4792
- 'apps/remotes/remote-commerce',
4793
- 'apps/remotes/remote-identity',
4794
- 'apps/remotes/remote-design-system'
4835
+ 'apps/remotes'
4795
4836
  ];
4796
4837
  return `import { execFileSync } from 'node:child_process';
4797
4838
  import fs from 'node:fs';
@@ -4828,7 +4869,7 @@ const activePnpmVersion = execFileSync('pnpm', ['--version'], {
4828
4869
 
4829
4870
  assert(
4830
4871
  activePnpmVersion === expectedPnpmVersion,
4831
- \`Generated workspace requires pnpm \${expectedPnpmVersion}; active pnpm is \${activePnpmVersion}. Run mise install, then rerun through mise exec -- pnpm ...\`,
4872
+ \`Generated workspace requires pnpm \${expectedPnpmVersion}; active pnpm is \${activePnpmVersion}. Run mise install, then rerun pnpm from the activated shell.\`,
4832
4873
  );
4833
4874
 
4834
4875
  const requiredPaths = [
@@ -4884,7 +4925,7 @@ for (const vertical of fullStackVerticals) {
4884
4925
  \`\${vertical.path}/src/effect/\${vertical.stem}-client.ts\`,
4885
4926
  \`\${vertical.path}/src/modern-app-env.d.ts\`,
4886
4927
  \`\${vertical.path}/src/modern.runtime.ts\`,
4887
- \`\${vertical.path}/src/remote-entry.tsx\`,
4928
+ \`\${vertical.path}/src/federation-entry.tsx\`,
4888
4929
  ...vertical.componentPaths,
4889
4930
  \`\${vertical.path}/locales/en/translation.json\`,
4890
4931
  \`\${vertical.path}/locales/en/\${vertical.namespace}.json\`,
@@ -4933,18 +4974,21 @@ assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'ins
4933
4974
  assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
4934
4975
  assert(
4935
4976
  rootPackage.scripts?.build ===
4936
- 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
4937
- 'Root build script must build remotes before shell',
4977
+ 'pnpm -r --filter "./verticals/*" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
4978
+ 'Root build script must build verticals before shell',
4938
4979
  );
4939
4980
  assert(rootPackage.scripts?.['ultramodern:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose ultramodern:check');
4940
4981
  assert(rootPackage.scripts?.['ultramodern:assert-mf-types'] === 'node ./scripts/assert-mf-types.mjs', 'Root must expose ultramodern:assert-mf-types');
4941
4982
  assert(rootPackage.scripts?.['cloudflare:deploy']?.includes('run cloudflare:deploy'), 'Root must expose cloudflare:deploy');
4942
4983
  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
+ assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
4985
+ assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
4986
+ assert(rootPackage.scripts?.postinstall === 'node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true) && node ./scripts/setup-agent-reference-repos.mjs', 'Root postinstall must bootstrap agent skills and hooks before reference repositories');
4943
4987
 
4944
4988
  const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
4945
4989
  assert(
4946
4990
  JSON.stringify(generatedContract.apps?.map(app => app.id)) === JSON.stringify(expectedAppIds),
4947
- 'Generated contract must contain shell plus the Tractor full-stack remotes',
4991
+ 'Generated contract must contain shell plus the Tractor full-stack verticals',
4948
4992
  );
4949
4993
  assert(generatedContract.cssFederation?.sharedDesignTokens?.owner?.id === 'shared-design-tokens', 'CSS federation must declare shared design token ownership');
4950
4994
  assert(generatedContract.cssFederation?.sharedDesignTokens?.role === 'shared-design-tokens', 'CSS federation must mark shared-design-tokens as token owner');
@@ -4966,7 +5010,7 @@ const expectedZephyrDependencies = Object.fromEntries(
4966
5010
  assert(
4967
5011
  JSON.stringify(shellPackage['zephyr:dependencies']) ===
4968
5012
  JSON.stringify(expectedZephyrDependencies),
4969
- 'Shell Zephyr dependencies must reference every Tractor remote package',
5013
+ 'Shell Zephyr dependencies must reference every Tractor vertical package',
4970
5014
  );
4971
5015
  const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
4972
5016
  assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
@@ -4975,7 +5019,7 @@ assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-supe
4975
5019
  assert(shellContract?.styling?.federation?.owner?.id === 'shell-super-app', 'Shell CSS federation owner is missing');
4976
5020
  assert(shellContract?.styling?.federation?.role === 'shell-base-overlay', 'Shell must own base and overlay CSS');
4977
5021
  assert(shellContract?.styling?.federation?.rootSelector === '[data-app-id="shell-super-app"]', 'Shell CSS root selector is incorrect');
4978
- assert(shellContract?.styling?.federation?.classPrefix === 'shell-', 'Shell CSS class prefix is incorrect');
5022
+ assert(shellContract?.styling?.federation?.classPrefix === 'shell:', 'Shell CSS class prefix is incorrect');
4979
5023
  assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), 'Shell must own the base CSS layer');
4980
5024
  assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-overlay'), 'Shell must own the overlay CSS layer');
4981
5025
  assert(shellContract?.styling?.federation?.entrypoints?.css?.includes('src/routes/index.css'), 'Shell CSS entrypoint is missing');
@@ -4983,23 +5027,24 @@ assert(shellContract?.styling?.federation?.assets?.shared?.some(asset => asset.e
4983
5027
  assert(shellContract?.styling?.federation?.dedupe?.duplicateBaseStylesAllowed === false, 'Shell CSS contract must forbid duplicated base styles');
4984
5028
  assert(shellContract?.styling?.federation?.ssr?.firstPaintRequired === true, 'Shell CSS must be required for SSR first paint');
4985
5029
  assert(
4986
- topology.shell?.remoteRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
4987
- 'Topology shell remoteRefs must match Tractor remotes',
5030
+ topology.shell?.verticalRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
5031
+ 'Topology shell verticalRefs must match Tractor verticals',
4988
5032
  );
4989
- assert(topology.remotes?.length === fullStackVerticals.length, 'Topology must contain only Tractor remotes');
4990
- assert((topology.effectServices ?? []).length === 0, 'Default APIs must be vertical-owned, not effectServices');
5033
+ assert(topology.verticals?.length === fullStackVerticals.length, 'Topology must contain only Tractor verticals');
5034
+ assert(!('remotes' in topology), 'Topology must not expose legacy remotes; use verticals');
5035
+ assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, not effectServices');
4991
5036
 
4992
5037
  for (const vertical of fullStackVerticals) {
4993
5038
  const packageJson = readJson(\`\${vertical.path}/package.json\`);
4994
5039
  assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
4995
- assert(packageJson.scripts?.['cloudflare:deploy'] === 'MODERNJS_DEPLOY=cloudflare modern deploy', \`\${vertical.id} must expose cloudflare:deploy\`);
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\`);
4996
5041
  assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
4997
5042
  assert(packageJson.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
4998
5043
  assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
4999
5044
  assert(packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts', \`\${vertical.id} must export its Effect API contract\`);
5000
5045
  const expectedVerticalZephyrDependencies = Object.fromEntries(
5001
5046
  fullStackVerticals
5002
- .filter(candidate => vertical.remoteRefs.includes(candidate.id))
5047
+ .filter(candidate => vertical.verticalRefs.includes(candidate.id))
5003
5048
  .map(candidate => [
5004
5049
  candidate.domain,
5005
5050
  \`\${candidate.packageName}@workspace:*\`,
@@ -5008,7 +5053,7 @@ for (const vertical of fullStackVerticals) {
5008
5053
  assert(
5009
5054
  JSON.stringify(packageJson['zephyr:dependencies']) ===
5010
5055
  JSON.stringify(expectedVerticalZephyrDependencies),
5011
- \`\${vertical.id} Zephyr dependencies must match declared MF remote refs\`,
5056
+ \`\${vertical.id} Zephyr dependencies must match declared vertical refs\`,
5012
5057
  );
5013
5058
 
5014
5059
  const contractEntry = generatedContract.apps?.find(app => app.id === vertical.id);
@@ -5020,11 +5065,11 @@ for (const vertical of fullStackVerticals) {
5020
5065
  assert(contractEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} MF name is incorrect\`);
5021
5066
  assert(JSON.stringify(contractEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} MF exposes are incorrect\`);
5022
5067
  assert(contractEntry?.moduleFederation?.dts?.compilerInstance === '--package typescript -- tsc', \`\${vertical.id} must keep mandatory DTS compiler\`);
5023
- assert(JSON.stringify(contractEntry?.moduleFederation?.remoteRefs ?? []) === JSON.stringify(vertical.remoteRefs), \`\${vertical.id} MF remoteRefs are incorrect\`);
5068
+ assert(JSON.stringify(contractEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), \`\${vertical.id} MF verticalRefs are incorrect\`);
5024
5069
  assert(
5025
5070
  JSON.stringify((contractEntry?.moduleFederation?.remotes ?? []).map(remote => remote.id)) ===
5026
- JSON.stringify(vertical.remoteRefs),
5027
- \`\${vertical.id} MF consumed remotes are incorrect\`,
5071
+ JSON.stringify(vertical.verticalRefs),
5072
+ \`\${vertical.id} MF consumed verticals are incorrect\`,
5028
5073
  );
5029
5074
  assert(contractEntry?.effect?.prefix === vertical.apiPrefix, \`\${vertical.id} Effect API prefix is incorrect\`);
5030
5075
  assert(contractEntry?.effect?.group === vertical.group, \`\${vertical.id} Effect group is incorrect\`);
@@ -5041,23 +5086,23 @@ for (const vertical of fullStackVerticals) {
5041
5086
  assert(contractEntry?.routes?.source === 'route-owned', \`\${vertical.id} routes must be route-owned\`);
5042
5087
  assert(contractEntry?.routes?.metadataExport === './src/routes/ultramodern-route-metadata', \`\${vertical.id} route metadata export is incorrect\`);
5043
5088
  assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
5044
- assert(contractEntry?.styling?.federation?.role === 'vertical-remote-css', \`\${vertical.id} must own only vertical CSS\`);
5089
+ assert(contractEntry?.styling?.federation?.role === 'vertical-css', \`\${vertical.id} must own only vertical CSS\`);
5045
5090
  assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
5046
- assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}-\`, \`\${vertical.id} CSS class prefix is incorrect\`);
5047
- assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-remote-\${vertical.domain}\`), \`\${vertical.id} remote CSS layer is missing\`);
5091
+ assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}:\`, \`\${vertical.id} CSS class prefix is incorrect\`);
5092
+ assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-vertical-\${vertical.domain}\`), \`\${vertical.id} vertical CSS layer is missing\`);
5048
5093
  assert(!contractEntry?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), \`\${vertical.id} must not own shell base CSS\`);
5049
- assert(contractEntry?.styling?.federation?.entrypoints?.remoteEntry === 'src/remote-entry.tsx', \`\${vertical.id} remote CSS contract must include remote entry\`);
5094
+ assert(contractEntry?.styling?.federation?.entrypoints?.federationEntry === 'src/federation-entry.tsx', \`\${vertical.id} CSS contract must include federation entry\`);
5050
5095
  assert(contractEntry?.styling?.federation?.assets?.shared?.some(asset => asset.endsWith('/shared-design-tokens/tokens.css')), \`\${vertical.id} must import shared design token CSS\`);
5051
5096
  assert(contractEntry?.styling?.federation?.dedupe?.runtimeLoad === 'once-per-content-hash', \`\${vertical.id} CSS dedupe strategy is incorrect\`);
5052
- assert(contractEntry?.styling?.federation?.ssr?.remoteCss === 'remote-manifest-owned-css', \`\${vertical.id} SSR CSS loading contract is incorrect\`);
5097
+ assert(contractEntry?.styling?.federation?.ssr?.verticalCss === 'federated-manifest-owned-css', \`\${vertical.id} SSR CSS loading contract is incorrect\`);
5053
5098
 
5054
- const topologyEntry = topology.remotes?.find(remote => remote.id === vertical.id);
5099
+ const topologyEntry = topology.verticals?.find(verticalEntry => verticalEntry.id === vertical.id);
5055
5100
  assert(topologyEntry?.kind === 'vertical', \`\${vertical.id} topology kind is incorrect\`);
5056
5101
  assert(topologyEntry?.package === vertical.packageName, \`\${vertical.id} topology package is incorrect\`);
5057
5102
  assert(topologyEntry?.cloudflare?.workerName === expectedWorkerName(vertical.id), \`\${vertical.id} topology Cloudflare workerName is incorrect\`);
5058
5103
  assert(topologyEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} topology MF name is incorrect\`);
5059
5104
  assert(JSON.stringify(topologyEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} topology exposes are incorrect\`);
5060
- assert(JSON.stringify(topologyEntry?.moduleFederation?.remoteRefs ?? []) === JSON.stringify(vertical.remoteRefs), \`\${vertical.id} topology remoteRefs are incorrect\`);
5105
+ assert(JSON.stringify(topologyEntry?.moduleFederation?.verticalRefs ?? []) === JSON.stringify(vertical.verticalRefs), \`\${vertical.id} topology verticalRefs are incorrect\`);
5061
5106
  assert(topologyEntry?.api?.effect?.bff?.prefix === vertical.apiPrefix, \`\${vertical.id} topology API prefix is incorrect\`);
5062
5107
  assert(topologyEntry?.api?.effect?.serverEntry === \`\${vertical.path}/api/effect/index.ts\`, \`\${vertical.id} topology server entry is incorrect\`);
5063
5108
  assert(topologyEntry?.api?.effect?.readiness?.endpoint === \`/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} topology readiness endpoint is incorrect\`);
@@ -5124,10 +5169,10 @@ function parseArgs(argv) {
5124
5169
 
5125
5170
  function printHelp() {
5126
5171
  process.stdout.write(\`Usage:
5127
- node scripts/proof-cloudflare-version.mjs [--app remote-explore] [--out evidence.json] [--require-public-urls]
5172
+ node scripts/proof-cloudflare-version.mjs [--app explore] [--out evidence.json] [--require-public-urls]
5128
5173
 
5129
5174
  Set each app's public URL using the contract env key, for example:
5130
- ULTRAMODERN_PUBLIC_URL_REMOTE_EXPLORE=https://remote-explore.example.workers.dev
5175
+ ULTRAMODERN_PUBLIC_URL_EXPLORE=https://explore.example.workers.dev
5131
5176
  \`);
5132
5177
  }
5133
5178
 
@@ -5392,12 +5437,15 @@ main().then(
5392
5437
  );
5393
5438
  `;
5394
5439
  }
5395
- function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = remoteApps) {
5440
+ function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = verticalApps) {
5396
5441
  writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript());
5397
5442
  writeFileReplacing(targetDir, "scripts/validate-ultramodern-workspace.mjs", createWorkspaceValidationScript(scope, enableTailwind, remotes));
5398
5443
  writeFileReplacing(targetDir, "scripts/proof-cloudflare-version.mjs", createCloudflareVersionProofScript());
5399
5444
  }
5400
5445
  function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
5446
+ const writeAppFile = (relativePath, content)=>{
5447
+ writeFile(targetDir, `${app.directory}/${relativePath}`, content);
5448
+ };
5401
5449
  writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
5402
5450
  writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
5403
5451
  writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, createAppEnvDts(app));
@@ -5414,75 +5462,40 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
5414
5462
  writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
5415
5463
  writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
5416
5464
  }
5417
- writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
5418
- writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
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));
5419
5467
  for (const [relativePath, content] of Object.entries(commerceAssetsForApp(app)))writeFile(targetDir, `${app.directory}/${relativePath}`, content);
5420
- writeFile(targetDir, `${app.directory}/src/routes/[lang]/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
5468
+ writeAppFile('src/routes/[lang]/page.tsx', 'shell' === app.kind ? createShellPage() : createRemotePage(app));
5421
5469
  for (const route of createRouteOwnedI18nPaths(app))if ('/' !== route.canonicalPath && 'shell' !== app.kind) writeFile(targetDir, createRoutePageFilePath(app, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
5422
5470
  if ('shell' === app.kind) {
5423
- writeFile(targetDir, `${app.directory}/src/routes/remote-components.tsx`, createShellRemoteComponents(scope));
5424
- writeFile(targetDir, `${app.directory}/src/routes/shell-frame.tsx`, createShellFrameComponent());
5425
- writeFile(targetDir, `${app.directory}/src/routes/boundary-overlay.tsx`, createShellBoundaryOverlay());
5471
+ writeAppFile('src/routes/vertical-components.tsx', createShellRemoteComponents(scope));
5472
+ writeAppFile('src/routes/shell-frame.tsx', createShellFrameComponent());
5473
+ writeAppFile('src/routes/boundary-overlay.tsx', createShellBoundaryOverlay());
5426
5474
  writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
5427
- writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/page.tsx`, createShellTractorsPage());
5428
- writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/[slug]/page.tsx`, createShellProductPage());
5429
- writeFile(targetDir, `${app.directory}/src/routes/[lang]/cart/page.tsx`, createShellCartPage());
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());
5430
5479
  }
5431
5480
  if (appHasEffectApi(app)) {
5432
5481
  writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
5433
5482
  writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
5434
5483
  writeFile(targetDir, `${app.directory}/src/effect/${app.effectApi.stem}-client.ts`, createEffectClient(app, '../../shared/effect/api'));
5435
5484
  }
5436
- if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
5437
- writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
5438
- if ('remote-decide' === app.id) writeFile(targetDir, `${app.directory}/src/components/remote-components.tsx`, createDecideRemoteComponents(scope));
5439
- if ('remote-checkout' === app.id) writeFile(targetDir, `${app.directory}/src/cart-store.ts`, createCheckoutCartStore());
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());
5440
5489
  for (const expose of Object.keys(app.exposes ?? {})){
5441
5490
  const outputPath = remoteComponentOutputPath(app, expose);
5442
- if (outputPath) writeFile(targetDir, outputPath, createRemoteExposeComponent(app, expose));
5491
+ if (outputPath) writeAppFile(outputPath.slice(app.directory.length + 1), createRemoteExposeComponent(app, expose));
5443
5492
  }
5444
5493
  }
5445
5494
  if ('horizontal-design-system' === app.kind) {
5446
- writeFile(targetDir, `${app.directory}/src/components/button.tsx`, createDesignButton());
5495
+ writeAppFile('src/components/button.tsx', createDesignButton(app));
5447
5496
  writeFile(targetDir, `${app.directory}/src/tokens.ts`, createDesignTokens());
5448
5497
  }
5449
5498
  }
5450
- function writeEffectService(targetDir, scope, packageSource, enableTailwind, service = effectService) {
5451
- writeJson(targetDir, `${service.directory}/package.json`, createServicePackage(scope, packageSource, enableTailwind, service));
5452
- writeJson(targetDir, `${service.directory}/tsconfig.json`, createPackageTsConfig(service.directory, true));
5453
- writeFile(targetDir, `${service.directory}/src/modern-app-env.d.ts`, "/// <reference types='@modern-js/app-tools/types' />\n");
5454
- writeFile(targetDir, `${service.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, service));
5455
- writeFile(targetDir, `${service.directory}/src/routes/layout.tsx`, createLayout(service.id));
5456
- writeFile(targetDir, `${service.directory}/src/routes/page.tsx`, `export default function ${toPascalCase(service.id)}Home() {
5457
- return (
5458
- <main className="min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8">
5459
- <section className="rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10">
5460
- <h1 className="text-3xl font-black">${service.id} Effect service</h1>
5461
- </section>
5462
- </main>
5463
- );
5464
- }
5465
- `);
5466
- writeFile(targetDir, `${service.directory}/src/routes/index.css`, createServiceStyles(enableTailwind, scope, service));
5467
- if (enableTailwind) {
5468
- writeFile(targetDir, `${service.directory}/postcss.config.mjs`, createPostcssConfig());
5469
- writeFile(targetDir, `${service.directory}/tailwind.config.ts`, createTailwindConfig());
5470
- }
5471
- writeFile(targetDir, `${service.directory}/modern.config.ts`, createServiceModernConfigFor(service));
5472
- writeFile(targetDir, `${service.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, service));
5473
- }
5474
- function writeGenericSharedPackage(targetDir, scope, packageSource, sharedPackage) {
5475
- writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
5476
- writeJson(targetDir, `${sharedPackage.directory}/tsconfig.json`, {
5477
- extends: `${relativeRootFor(sharedPackage.directory)}/tsconfig.base.json`,
5478
- include: [
5479
- 'src'
5480
- ]
5481
- });
5482
- writeFile(targetDir, `${sharedPackage.directory}/src/index.ts`, `export const packageId = '${sharedPackage.id}';
5483
- `);
5484
- if ('shared-design-tokens' === sharedPackage.id) writeFile(targetDir, `${sharedPackage.directory}/src/tokens.css`, createSharedDesignTokensCss());
5485
- }
5486
5499
  function writeSharedPackages(targetDir, scope, packageSource) {
5487
5500
  for (const sharedPackage of sharedPackages){
5488
5501
  writeJson(targetDir, `${sharedPackage.directory}/package.json`, createSharedPackage(scope, sharedPackage.id, sharedPackage.description, packageSource));
@@ -5516,18 +5529,6 @@ function readJsonFile(filePath) {
5516
5529
  function writeJsonFile(filePath, value) {
5517
5530
  node_fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
5518
5531
  }
5519
- function appendEffectSharedApiContract(targetDir, service = effectService) {
5520
- const relativePath = 'packages/shared-effect-api/src/index.ts';
5521
- assertSafeRelativePath(relativePath);
5522
- const filePath = node_path.join(targetDir, relativePath);
5523
- ensureInsideRoot(targetDir, filePath);
5524
- if (!node_fs.existsSync(filePath)) throw new Error(`Missing generated Effect API package: ${relativePath}`);
5525
- const current = node_fs.readFileSync(filePath, 'utf-8');
5526
- const apiExport = serviceEffectApiExport(service);
5527
- if (current.includes(`export const ${apiExport} =`)) return;
5528
- const contentWithImports = current.includes('@modern-js/plugin-bff/effect-client') ? current.trimEnd() : `${createEffectSharedApiImports()}\n${current.trimEnd()}`;
5529
- node_fs.writeFileSync(filePath, `${contentWithImports}\n\n${createEffectSharedApiContract(service)}`, 'utf-8');
5530
- }
5531
5532
  function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
5532
5533
  if (packageSource) return resolvePackageSource({
5533
5534
  targetDir: workspaceRoot,
@@ -5557,9 +5558,17 @@ function existingPackageSource(workspaceRoot, modernVersion, packageSource) {
5557
5558
  aliasPackageNamePrefix
5558
5559
  };
5559
5560
  }
5560
- function assertValidMicroVerticalName(name) {
5561
+ function existingTailwindEnabled(workspaceRoot) {
5562
+ const contractPath = node_path.join(workspaceRoot, GENERATED_CONTRACT_PATH);
5563
+ if (!node_fs.existsSync(contractPath)) return true;
5564
+ const contract = readJsonFile(contractPath);
5565
+ const apps = isRecord(contract) && Array.isArray(contract.apps) ? contract.apps : [];
5566
+ const shell = apps.find((app)=>isRecord(app) && app.id === shellApp.id);
5567
+ return shell?.styling && isRecord(shell.styling) ? false !== shell.styling.tailwind : true;
5568
+ }
5569
+ function assertValidVerticalName(name) {
5561
5570
  const normalized = toKebabCase(name);
5562
- if (!normalized || normalized !== name) throw new Error(`Invalid MicroVertical name "${name}". Use lowercase kebab-case.`);
5571
+ if (!normalized || normalized !== name) throw new Error(`Invalid Vertical name "${name}". Use lowercase kebab-case.`);
5563
5572
  return normalized;
5564
5573
  }
5565
5574
  function nextAvailablePort(ports) {
@@ -5595,30 +5604,31 @@ function addShellWorkspaceDependency(workspaceRoot, scope, remote) {
5595
5604
  shellPackage.dependencies[ultramodern_workspace_packageName(scope, remote.packageSuffix)] = WORKSPACE_PACKAGE_VERSION;
5596
5605
  writeJsonFile(packagePath, shellPackage);
5597
5606
  }
5598
- function remoteTopologyEntry(scope, remote) {
5607
+ function verticalTopologyEntry(scope, vertical) {
5599
5608
  return {
5600
- id: remote.id,
5601
- kind: remote.kind,
5602
- domain: remote.domain,
5603
- package: ultramodern_workspace_packageName(scope, remote.packageSuffix),
5609
+ id: vertical.id,
5610
+ kind: vertical.kind,
5611
+ domain: vertical.domain,
5612
+ package: ultramodern_workspace_packageName(scope, vertical.packageSuffix),
5613
+ path: vertical.directory,
5604
5614
  moduleFederation: {
5605
5615
  role: 'remote',
5606
- name: remote.mfName,
5607
- manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
5608
- exposes: Object.keys(remote.exposes ?? {}),
5609
- ...remote.remoteRefs?.length ? {
5610
- remoteRefs: remote.remoteRefs,
5611
- remotes: createModuleFederationRemoteContracts(remote)
5616
+ name: vertical.mfName,
5617
+ manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`,
5618
+ exposes: Object.keys(vertical.exposes ?? {}),
5619
+ ...vertical.verticalRefs?.length ? {
5620
+ verticalRefs: vertical.verticalRefs,
5621
+ remotes: createModuleFederationRemoteContracts(vertical)
5612
5622
  } : {},
5613
5623
  ssr: true,
5614
5624
  fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
5615
5625
  sharedContractVersion: 'mf-ssr-contract-v1'
5616
5626
  },
5617
- ...effectApiTopologyMetadata(remote) ? {
5618
- api: effectApiTopologyMetadata(remote)
5627
+ ...effectApiTopologyMetadata(vertical) ? {
5628
+ api: effectApiTopologyMetadata(vertical)
5619
5629
  } : {},
5620
- cloudflare: createCloudflareDeployContract(scope, remote),
5621
- ownership: remote.ownership
5630
+ cloudflare: createCloudflareDeployContract(scope, vertical),
5631
+ ownership: vertical.ownership
5622
5632
  };
5623
5633
  }
5624
5634
  function ownershipEntry(scope, owner) {
@@ -5629,47 +5639,48 @@ function ownershipEntry(scope, owner) {
5629
5639
  ownership: owner.ownership
5630
5640
  };
5631
5641
  }
5632
- function remotesFromTopology(topology, ports) {
5633
- return (topology.remotes ?? []).map((remote)=>{
5634
- const packageSuffix = remote.package?.split('/').at(-1) ?? remote.id;
5635
- const effectApi = remote.api?.effect ? {
5636
- stem: 'string' == typeof remote.api.effect.basePath ? remote.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? remote.domain ?? String(remote.id).replace(/^remote-/, '') : remote.domain ?? String(remote.id).replace(/^remote-/, ''),
5637
- prefix: remote.api.effect.bff?.prefix ?? `/${remote.domain ?? String(remote.id).replace(/^remote-/, '')}-api`,
5638
- consumedBy: Array.isArray(remote.api.effect.consumedBy) ? remote.api.effect.consumedBy : [
5642
+ function verticalsFromTopology(topology, ports) {
5643
+ return (topology.verticals ?? []).map((vertical)=>{
5644
+ const domain = vertical.domain ?? String(vertical.id);
5645
+ const packageSuffix = vertical.package?.split('/').at(-1) ?? domain;
5646
+ const effectApi = vertical.api?.effect ? {
5647
+ stem: 'string' == typeof vertical.api.effect.basePath ? vertical.api.effect.basePath.split('/').filter(Boolean).at(-1) ?? domain : domain,
5648
+ prefix: vertical.api.effect.bff?.prefix ?? `/${domain}-api`,
5649
+ consumedBy: Array.isArray(vertical.api.effect.consumedBy) ? vertical.api.effect.consumedBy : [
5639
5650
  shellApp.id,
5640
- remote.id
5651
+ vertical.id
5641
5652
  ]
5642
5653
  } : void 0;
5643
5654
  return {
5644
- id: remote.id,
5645
- directory: 'string' == typeof remote.path ? remote.path : `apps/remotes/${packageSuffix}`,
5655
+ id: vertical.id,
5656
+ directory: 'string' == typeof vertical.path ? vertical.path : `verticals/${domain}`,
5646
5657
  packageSuffix,
5647
- displayName: remote.id,
5648
- kind: remote.kind ?? 'vertical',
5649
- domain: remote.domain ?? String(remote.id).replace(/^remote-/, ''),
5658
+ displayName: vertical.displayName ?? `${toPascalCase(domain)} Vertical`,
5659
+ kind: 'vertical',
5660
+ domain,
5650
5661
  portEnv: '',
5651
- port: 'number' == typeof ports[remote.id] ? ports[remote.id] : 0,
5652
- mfName: remote.moduleFederation?.name ?? `remote${toPascalCase(remote.id)}`,
5653
- ...Array.isArray(remote.moduleFederation?.exposes) ? {
5654
- exposes: Object.fromEntries(remote.moduleFederation.exposes.map((expose)=>[
5662
+ port: 'number' == typeof ports[vertical.id] ? ports[vertical.id] : 0,
5663
+ mfName: vertical.moduleFederation?.name ?? `vertical${toPascalCase(domain)}`,
5664
+ ...Array.isArray(vertical.moduleFederation?.exposes) ? {
5665
+ exposes: Object.fromEntries(vertical.moduleFederation.exposes.map((expose)=>[
5655
5666
  expose,
5656
5667
  ''
5657
5668
  ]))
5658
5669
  } : {},
5659
- ...Array.isArray(remote.moduleFederation?.remoteRefs) ? {
5660
- remoteRefs: remote.moduleFederation.remoteRefs
5661
- } : Array.isArray(remote.moduleFederation?.remotes) ? {
5662
- remoteRefs: remote.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
5670
+ ...Array.isArray(vertical.moduleFederation?.verticalRefs) ? {
5671
+ verticalRefs: vertical.moduleFederation.verticalRefs
5672
+ } : Array.isArray(vertical.moduleFederation?.remotes) ? {
5673
+ verticalRefs: vertical.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
5663
5674
  } : {},
5664
5675
  ...effectApi ? {
5665
5676
  effectApi
5666
5677
  } : {},
5667
- ownership: remote.ownership ?? createNeutralOwnership(remote.id)
5678
+ ownership: vertical.ownership ?? createNeutralOwnership(vertical.id)
5668
5679
  };
5669
5680
  });
5670
5681
  }
5671
- function addUltramodernMicroVertical(options) {
5672
- const name = assertValidMicroVerticalName(options.name);
5682
+ function addUltramodernVertical(options) {
5683
+ const name = assertValidVerticalName(options.name);
5673
5684
  const rootPackage = readJsonFile(node_path.join(options.workspaceRoot, 'package.json'));
5674
5685
  const scope = toPackageScope(String(rootPackage.name ?? node_path.basename(options.workspaceRoot)));
5675
5686
  const topologyPath = node_path.join(options.workspaceRoot, 'topology/reference-topology.json');
@@ -5685,124 +5696,63 @@ function addUltramodernMicroVertical(options) {
5685
5696
  const overlay = readJsonFile(overlayPath);
5686
5697
  overlay.ports ??= {};
5687
5698
  const packageSource = existingPackageSource(options.workspaceRoot, options.modernVersion, options.packageSource);
5688
- const enableTailwind = false !== options.enableTailwind;
5699
+ const enableTailwind = options.enableTailwind ?? existingTailwindEnabled(options.workspaceRoot);
5689
5700
  const port = nextAvailablePort(overlay.ports);
5690
- if ('remote' === options.kind || 'horizontal-remote' === options.kind) {
5691
- const remote = createRemoteDescriptor(name, options.kind, port);
5692
- assertCanCreate(options.workspaceRoot, remote.directory);
5693
- if ((topology.remotes ?? []).some((entry)=>entry.id === remote.id)) throw new Error(`Topology already contains ${remote.id}`);
5694
- if (Object.values(overlay.ports).includes(remote.port)) throw new Error(`Development port ${remote.port} is already in use`);
5695
- writeApp(options.workspaceRoot, scope, remote, packageSource, enableTailwind);
5696
- topology.shell ??= {};
5697
- topology.shell.remoteRefs ??= [];
5698
- topology.shell.remoteRefs.push(remote.id);
5699
- topology.shell.moduleFederation ??= {};
5700
- topology.shell.moduleFederation.remotes ??= [];
5701
- topology.shell.moduleFederation.remotes.push({
5702
- id: remote.id,
5703
- name: remote.mfName,
5704
- manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
5705
- });
5706
- topology.remotes ??= [];
5707
- topology.remotes.push(remoteTopologyEntry(scope, remote));
5708
- ownership.owners ??= [];
5709
- ownership.owners.push(ownershipEntry(scope, remote));
5710
- overlay.ports[remote.id] = remote.port;
5711
- overlay.manifests ??= {};
5712
- overlay.manifests[remote.id] = `http://localhost:${remote.port}/mf-manifest.json`;
5713
- if (appHasEffectApi(remote)) {
5714
- overlay.apis ??= {};
5715
- overlay.apis[remote.id] = `http://localhost:${remote.port}${effectApiPrefix(remote)}`;
5716
- }
5717
- writeJsonFile(topologyPath, topology);
5718
- writeJsonFile(ownershipPath, ownership);
5719
- writeJsonFile(overlayPath, overlay);
5720
- const updatedRemotes = remotesFromTopology(topology, overlay.ports);
5721
- writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
5722
- {
5723
- ...shellApp,
5724
- remoteRefs: updatedRemotes.map((remote)=>remote.id)
5725
- },
5726
- ...updatedRemotes
5727
- ], enableTailwind));
5728
- const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
5729
- writeFileReplacing(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`, createShellModuleFederationConfig(updatedRemotes));
5730
- if (!node_fs.existsSync(shellConfigPath)) throw new Error('Shell Module Federation config was not regenerated');
5731
- writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedRemotes);
5732
- addShellZephyrDependency(options.workspaceRoot, scope, remote);
5733
- addShellWorkspaceDependency(options.workspaceRoot, scope, remote);
5734
- addRootDevScript(options.workspaceRoot, scope, remote.packageSuffix, name);
5735
- return;
5736
- }
5737
- if ('service' === options.kind) {
5738
- const service = createServiceDescriptor(name, port);
5739
- assertCanCreate(options.workspaceRoot, service.directory);
5740
- if ((topology.effectServices ?? []).some((entry)=>entry.id === service.id)) throw new Error(`Topology already contains ${service.id}`);
5741
- writeEffectService(options.workspaceRoot, scope, packageSource, enableTailwind, service);
5742
- appendEffectSharedApiContract(options.workspaceRoot, service);
5743
- topology.effectServices ??= [];
5744
- topology.effectServices.push({
5745
- id: service.id,
5746
- kind: 'effect-service',
5747
- runtime: 'effect',
5748
- package: ultramodern_workspace_packageName(scope, service.packageSuffix),
5749
- consumedBy: [
5750
- shellApp.id
5751
- ],
5752
- bff: {
5753
- prefix: serviceApiPrefix(service),
5754
- openapi: '/openapi.json'
5755
- },
5756
- contract: {
5757
- package: ultramodern_workspace_packageName(scope, 'shared-effect-api'),
5758
- export: serviceEffectApiExport(service),
5759
- path: 'packages/shared-effect-api/src/index.ts'
5760
- },
5761
- serverEntry: `${service.directory}/api/effect/index.ts`,
5762
- basePath: `${serviceApiPrefix(service)}/effect/${effectApiStem(service)}`,
5763
- ...createEffectOperationContract(service),
5764
- ownership: service.ownership
5765
- });
5766
- ownership.owners ??= [];
5767
- ownership.owners.push(ownershipEntry(scope, service));
5768
- overlay.ports[service.id] = service.port;
5769
- overlay.services ??= {};
5770
- overlay.services[service.id] = `http://localhost:${service.port}${serviceApiPrefix(service)}`;
5771
- writeJsonFile(topologyPath, topology);
5772
- writeJsonFile(ownershipPath, ownership);
5773
- writeJsonFile(overlayPath, overlay);
5774
- addRootDevScript(options.workspaceRoot, scope, service.packageSuffix, name);
5775
- return;
5776
- }
5777
- if ('shared' === options.kind) {
5778
- const sharedPackage = createSharedPackageDescriptor(name);
5779
- assertCanCreate(options.workspaceRoot, sharedPackage.directory);
5780
- if ((topology.sharedPackages ?? []).some((entry)=>entry.id === sharedPackage.id)) throw new Error(`Topology already contains ${sharedPackage.id}`);
5781
- writeGenericSharedPackage(options.workspaceRoot, scope, packageSource, sharedPackage);
5782
- topology.sharedPackages ??= [];
5783
- topology.sharedPackages.push({
5784
- id: sharedPackage.id,
5785
- package: ultramodern_workspace_packageName(scope, sharedPackage.id),
5786
- path: sharedPackage.directory,
5787
- description: sharedPackage.description
5788
- });
5789
- ownership.owners ??= [];
5790
- ownership.owners.push(ownershipEntry(scope, {
5791
- id: sharedPackage.id,
5792
- packageSuffix: sharedPackage.id,
5793
- directory: sharedPackage.directory,
5794
- ownership: createNeutralOwnership(sharedPackage.id, 'tier-1-shared-contract')
5795
- }));
5796
- writeJsonFile(topologyPath, topology);
5797
- writeJsonFile(ownershipPath, ownership);
5798
- return;
5799
- }
5800
- throw new Error(`Unsupported MicroVertical kind: ${options.kind}`);
5701
+ const vertical = createVerticalDescriptor(name, port);
5702
+ assertCanCreate(options.workspaceRoot, vertical.directory);
5703
+ if ((topology.verticals ?? []).some((entry)=>entry.id === vertical.id)) throw new Error(`Topology already contains ${vertical.id}`);
5704
+ if (Object.values(overlay.ports).includes(vertical.port)) throw new Error(`Development port ${vertical.port} is already in use`);
5705
+ writeApp(options.workspaceRoot, scope, vertical, packageSource, enableTailwind);
5706
+ topology.shell ??= {};
5707
+ topology.shell.verticalRefs ??= [];
5708
+ topology.shell.verticalRefs.push(vertical.id);
5709
+ topology.shell.moduleFederation ??= {};
5710
+ topology.shell.moduleFederation.remotes ??= [];
5711
+ topology.shell.moduleFederation.remotes.push({
5712
+ id: vertical.id,
5713
+ name: vertical.mfName,
5714
+ manifestUrl: `http://localhost:${vertical.port}/mf-manifest.json`
5715
+ });
5716
+ topology.verticals ??= [];
5717
+ topology.verticals.push(verticalTopologyEntry(scope, vertical));
5718
+ ownership.owners ??= [];
5719
+ ownership.owners.push(ownershipEntry(scope, vertical));
5720
+ overlay.ports[vertical.id] = vertical.port;
5721
+ overlay.manifests ??= {};
5722
+ overlay.manifests[vertical.id] = `http://localhost:${vertical.port}/mf-manifest.json`;
5723
+ overlay.apis ??= {};
5724
+ overlay.apis[vertical.id] = `http://localhost:${vertical.port}${effectApiPrefix(vertical)}`;
5725
+ writeJsonFile(topologyPath, topology);
5726
+ writeJsonFile(ownershipPath, ownership);
5727
+ writeJsonFile(overlayPath, overlay);
5728
+ const updatedVerticals = verticalsFromTopology(topology, overlay.ports);
5729
+ assertUniqueTailwindPrefixes([
5730
+ shellApp,
5731
+ ...updatedVerticals
5732
+ ]);
5733
+ writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
5734
+ {
5735
+ ...shellApp,
5736
+ verticalRefs: updatedVerticals.map((vertical)=>vertical.id)
5737
+ },
5738
+ ...updatedVerticals
5739
+ ], 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');
5743
+ writeGeneratedWorkspaceScripts(options.workspaceRoot, scope, enableTailwind, updatedVerticals);
5744
+ addShellZephyrDependency(options.workspaceRoot, scope, vertical);
5745
+ addShellWorkspaceDependency(options.workspaceRoot, scope, vertical);
5746
+ addRootDevScript(options.workspaceRoot, scope, vertical.packageSuffix, name);
5801
5747
  }
5802
5748
  function generateUltramodernWorkspace(options) {
5803
5749
  const scope = toPackageScope(options.packageName);
5804
5750
  const packageSource = resolvePackageSource(options);
5805
5751
  const enableTailwind = false !== options.enableTailwind;
5752
+ assertUniqueTailwindPrefixes([
5753
+ shellApp,
5754
+ ...verticalApps
5755
+ ]);
5806
5756
  node_fs.mkdirSync(options.targetDir, {
5807
5757
  recursive: true
5808
5758
  });
@@ -5821,10 +5771,10 @@ function generateUltramodernWorkspace(options) {
5821
5771
  writeJson(options.targetDir, '.modernjs/ultramodern-package-source.json', createPackageSourceMetadata(scope, packageSource));
5822
5772
  writeJson(options.targetDir, GENERATED_CONTRACT_PATH, createGeneratedContract(scope, [
5823
5773
  shellApp,
5824
- ...remoteApps
5774
+ ...verticalApps
5825
5775
  ], enableTailwind));
5826
5776
  writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
5827
- for (const remote of remoteApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
5777
+ for (const remote of verticalApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
5828
5778
  writeSharedPackages(options.targetDir, scope, packageSource);
5829
5779
  writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind);
5830
5780
  }
@@ -5839,7 +5789,7 @@ const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
5839
5789
  const src_TANSTACK_ROUTER_VERSION = '1.170.8';
5840
5790
  const src_TAILWIND_VERSION = '4.3.0';
5841
5791
  const src_TAILWIND_POSTCSS_VERSION = '4.3.0';
5842
- const src_PNPM_VERSION = '11.4.0';
5792
+ const src_PNPM_VERSION = '11.5.0';
5843
5793
  const requiredDeniedPaths = [
5844
5794
  '.git/**',
5845
5795
  '.npmrc',
@@ -5852,9 +5802,11 @@ const requiredDeniedPaths = [
5852
5802
  const requiredLifecycleDeniedScripts = [
5853
5803
  'preinstall',
5854
5804
  'install',
5855
- 'postinstall',
5856
5805
  'prepare'
5857
5806
  ];
5807
+ const requiredLifecycleAllowedScripts = [
5808
+ 'postinstall'
5809
+ ];
5858
5810
  function getOptionValue(args, names) {
5859
5811
  for (const name of names){
5860
5812
  const prefix = `${name}=`;
@@ -6024,6 +5976,7 @@ function createBuiltinTemplateManifest(version) {
6024
5976
  allowedPaths: [
6025
5977
  '.agents/**',
6026
5978
  '.browserslistrc',
5979
+ '.codex/**',
6027
5980
  '.github/**',
6028
5981
  '.gitignore',
6029
5982
  '.mise.toml',
@@ -6033,6 +5986,7 @@ function createBuiltinTemplateManifest(version) {
6033
5986
  'README.md',
6034
5987
  'api/**',
6035
5988
  'config/**',
5989
+ 'lefthook.yml',
6036
5990
  'modern.config.ts',
6037
5991
  'oxfmt.config.ts',
6038
5992
  'oxlint.config.ts',
@@ -6053,7 +6007,7 @@ function createBuiltinTemplateManifest(version) {
6053
6007
  lifecyclePolicy: {
6054
6008
  denyByDefault: true,
6055
6009
  deniedScripts: requiredLifecycleDeniedScripts,
6056
- allowedScripts: [],
6010
+ allowedScripts: requiredLifecycleAllowedScripts,
6057
6011
  requiresExplicitOptIn: true
6058
6012
  },
6059
6013
  validation: {
@@ -6072,7 +6026,7 @@ function createBuiltinTemplateManifest(version) {
6072
6026
  ],
6073
6027
  postMaterializationValidation: [
6074
6028
  'ultramodern-contract-check',
6075
- 'dependency-install-with-lifecycle-deny',
6029
+ 'agent-skill-postinstall-allowed',
6076
6030
  'github-workflow-security-enforced',
6077
6031
  'package-source-retained',
6078
6032
  'pnpm-11-policy-enforced',
@@ -6081,9 +6035,9 @@ function createBuiltinTemplateManifest(version) {
6081
6035
  ],
6082
6036
  expectedCommands: [
6083
6037
  'mise install',
6084
- 'mise exec -- pnpm install',
6085
- 'mise exec -- pnpm test',
6086
- 'mise exec -- pnpm run ultramodern:check'
6038
+ 'pnpm install',
6039
+ 'pnpm test',
6040
+ 'pnpm run ultramodern:check'
6087
6041
  ]
6088
6042
  }
6089
6043
  };
@@ -6146,7 +6100,7 @@ function validateTemplateManifest(manifest) {
6146
6100
  assertTemplateManifest(!manifest.materialization.overwritePolicy || 'deny-existing' === manifest.materialization.overwritePolicy || 'allow-generated-only' === manifest.materialization.overwritePolicy, 'materialization.overwritePolicy is unsupported');
6147
6101
  assertTemplateManifest(true === manifest.lifecyclePolicy.denyByDefault, 'lifecyclePolicy.denyByDefault must be true');
6148
6102
  for (const scriptName of requiredLifecycleDeniedScripts)assertTemplateManifest(manifest.lifecyclePolicy.deniedScripts.includes(scriptName), `lifecyclePolicy.deniedScripts must include ${scriptName}`);
6149
- assertTemplateManifest(0 === manifest.lifecyclePolicy.allowedScripts.length, 'lifecyclePolicy.allowedScripts must be empty for builtin materialization');
6103
+ assertTemplateManifest(JSON.stringify(manifest.lifecyclePolicy.allowedScripts) === JSON.stringify(requiredLifecycleAllowedScripts), 'lifecyclePolicy.allowedScripts must only allow generated postinstall');
6150
6104
  assertTemplateManifest(true === manifest.validation.schemaValidation, 'validation.schemaValidation must be true');
6151
6105
  for (const token of [
6152
6106
  'source-type-supported',
@@ -6227,7 +6181,7 @@ function showHelp() {
6227
6181
  if (localeKeys.help.optionUltramodernPackageSource) console.log(i18n.t(localeKeys.help.optionUltramodernPackageSource));
6228
6182
  if (localeKeys.help.optionUltramodernPackageScope) console.log(i18n.t(localeKeys.help.optionUltramodernPackageScope));
6229
6183
  if (localeKeys.help.optionUltramodernPackageNamePrefix) console.log(i18n.t(localeKeys.help.optionUltramodernPackageNamePrefix));
6230
- if (localeKeys.help.optionMicroVertical) console.log(i18n.t(localeKeys.help.optionMicroVertical));
6184
+ if (localeKeys.help.optionVertical) console.log(i18n.t(localeKeys.help.optionVertical));
6231
6185
  console.log(i18n.t(localeKeys.help.optionSub));
6232
6186
  console.log('');
6233
6187
  console.log(i18n.t(localeKeys.help.examples));
@@ -6269,18 +6223,22 @@ function detectTailwindFlag() {
6269
6223
  const args = process.argv.slice(2);
6270
6224
  return !args.includes('--no-tailwind');
6271
6225
  }
6226
+ function detectExplicitTailwindFlag() {
6227
+ const args = process.argv.slice(2);
6228
+ if (args.includes('--no-tailwind')) return false;
6229
+ if (args.includes('--tailwind')) return true;
6230
+ }
6272
6231
  function detectWorkspaceProtocolFlag() {
6273
6232
  const args = process.argv.slice(2);
6274
6233
  return args.includes('--workspace');
6275
6234
  }
6276
- function detectMicroVerticalKind() {
6277
- const kind = getOptionValue(process.argv.slice(2), [
6278
- '--microvertical'
6279
- ]);
6280
- if (!kind) return;
6281
- if ('remote' === kind || 'horizontal-remote' === kind || 'service' === kind || 'shared' === kind) return kind;
6282
- console.error('--microvertical must be one of: remote, horizontal-remote, service, shared');
6283
- process.exit(1);
6235
+ function detectVerticalFlag() {
6236
+ const args = process.argv.slice(2);
6237
+ if (args.some((arg)=>arg.startsWith('--vertical='))) {
6238
+ console.error('--vertical does not accept a value. Use: create <name> --vertical');
6239
+ process.exit(1);
6240
+ }
6241
+ return args.includes('--vertical');
6284
6242
  }
6285
6243
  function detectUltramodernWorkspaceFlag(createPackage) {
6286
6244
  const args = process.argv.slice(2);
@@ -6382,8 +6340,7 @@ async function getProjectName() {
6382
6340
  '--ultramodern-package-version',
6383
6341
  '--ultramodern-package-registry',
6384
6342
  '--ultramodern-package-scope',
6385
- '--ultramodern-package-name-prefix',
6386
- '--microvertical'
6343
+ '--ultramodern-package-name-prefix'
6387
6344
  ]);
6388
6345
  const optionWithoutValue = new Set([
6389
6346
  '--help',
@@ -6398,6 +6355,7 @@ async function getProjectName() {
6398
6355
  '--tailwind',
6399
6356
  '--no-tailwind',
6400
6357
  '--workspace',
6358
+ '--vertical',
6401
6359
  ULTRAMODERN_WORKSPACE_FLAG
6402
6360
  ]);
6403
6361
  const positionalArgs = [];
@@ -6408,9 +6366,13 @@ async function getProjectName() {
6408
6366
  i += 1;
6409
6367
  continue;
6410
6368
  }
6411
- if (!(arg.startsWith('--lang=') || arg.startsWith('--router=') || arg.startsWith('--bff-runtime=') || arg.startsWith('--ultramodern-package-source=') || arg.startsWith('--ultramodern-package-version=') || arg.startsWith('--ultramodern-package-registry=') || arg.startsWith('--ultramodern-package-scope=') || arg.startsWith('--ultramodern-package-name-prefix=') || arg.startsWith('--microvertical='))) positionalArgs.push(arg);
6369
+ if (!(arg.startsWith('--lang=') || arg.startsWith('--router=') || arg.startsWith('--bff-runtime=') || arg.startsWith('--ultramodern-package-source=') || arg.startsWith('--ultramodern-package-version=') || arg.startsWith('--ultramodern-package-registry=') || arg.startsWith('--ultramodern-package-scope=') || arg.startsWith('--ultramodern-package-name-prefix='))) positionalArgs.push(arg);
6412
6370
  }
6413
6371
  }
6372
+ if (positionalArgs.length > 1) {
6373
+ console.error(`Unexpected positional argument: ${positionalArgs[1]}`);
6374
+ process.exit(1);
6375
+ }
6414
6376
  const projectNameArg = positionalArgs[0];
6415
6377
  if (projectNameArg) return {
6416
6378
  name: projectNameArg,
@@ -6442,22 +6404,20 @@ async function main() {
6442
6404
  const createPackage = readCreatePackageJson();
6443
6405
  const version = createPackage.version || 'latest';
6444
6406
  const ultramodernPackageVersion = isBleedingDevCreatePackage(createPackage) ? getBleedingDevFrameworkVersion(createPackage, version) : version;
6445
- const microVerticalKind = detectMicroVerticalKind();
6446
- if (microVerticalKind) {
6407
+ const addVertical = detectVerticalFlag();
6408
+ if (addVertical) {
6447
6409
  const overridePackageSource = args.some((arg)=>arg.startsWith('--ultramodern-package-')) ? detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage) : void 0;
6448
- addUltramodernMicroVertical({
6410
+ addUltramodernVertical({
6449
6411
  workspaceRoot: process.cwd(),
6450
6412
  name: generatedPackageName,
6451
- kind: microVerticalKind,
6452
6413
  modernVersion: version,
6453
- enableTailwind: detectTailwindFlag(),
6414
+ enableTailwind: detectExplicitTailwindFlag(),
6454
6415
  packageSource: overridePackageSource
6455
6416
  });
6456
6417
  const dim = '\x1b[2m\x1b[3m';
6457
6418
  const reset = '\x1b[0m';
6458
6419
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
6459
- console.log(`${dim} mise install${reset}`);
6460
- console.log(`${dim} mise exec -- pnpm ultramodern:check${reset}\n`);
6420
+ console.log(`${dim} pnpm ultramodern:check${reset}\n`);
6461
6421
  return;
6462
6422
  }
6463
6423
  if (node_fs.existsSync(targetDir)) {
@@ -6485,9 +6445,8 @@ async function main() {
6485
6445
  if (!useCurrentDir) console.log(`${dim} ${i18n.t(localeKeys.message.step1, {
6486
6446
  projectName
6487
6447
  })}${reset}`);
6488
- console.log(`${dim} mise install${reset}`);
6489
6448
  console.log(`${dim} ${i18n.t(localeKeys.message.step2)}${reset}`);
6490
- console.log(`${dim} mise exec -- pnpm ultramodern:check${reset}`);
6449
+ console.log(`${dim} pnpm ultramodern:check${reset}`);
6491
6450
  console.log(`${dim} ${i18n.t(localeKeys.message.step3)}${reset}\n`);
6492
6451
  return;
6493
6452
  }
@@ -6542,14 +6501,23 @@ async function main() {
6542
6501
  delete packageJson.scripts['lint:fix'];
6543
6502
  delete packageJson.scripts['skills:install'];
6544
6503
  delete packageJson.scripts['skills:check'];
6504
+ delete packageJson.scripts.postinstall;
6545
6505
  }
6546
6506
  if (packageJson.devDependencies) {
6547
6507
  delete packageJson.devDependencies['lint-staged'];
6508
+ delete packageJson.devDependencies.lefthook;
6548
6509
  delete packageJson.devDependencies['simple-git-hooks'];
6549
6510
  delete packageJson.devDependencies.oxlint;
6550
6511
  delete packageJson.devDependencies.oxfmt;
6551
6512
  delete packageJson.devDependencies.ultracite;
6552
6513
  }
6514
+ node_fs.rmSync(node_path.join(targetDir, '.codex'), {
6515
+ recursive: true,
6516
+ force: true
6517
+ });
6518
+ node_fs.rmSync(node_path.join(targetDir, 'lefthook.yml'), {
6519
+ force: true
6520
+ });
6553
6521
  }
6554
6522
  node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
6555
6523
  writeTemplateManifestEvidence(targetDir, templateManifest);