@bleedingdev/modern-js-create 3.2.0-ultramodern.62 → 3.2.0-ultramodern.64

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/README.md CHANGED
@@ -156,6 +156,10 @@ CSS federation is explicit:
156
156
  - Tailwind CSS v4 is configured per app through `@tailwindcss/postcss`.
157
157
  - Duplicate base styles are forbidden; SSR first paint depends on shared token
158
158
  CSS plus Modern/Rspack-emitted app CSS.
159
+ - Apps should not inject remote `async-index.css` paths, hardcode remote
160
+ stylesheet links, or disable filename hashing to make CSS URLs predictable.
161
+ UltraModern SSR resolves federated CSS from build output and MF manifests so
162
+ generated shells and demos can keep normal hashed assets.
159
163
 
160
164
  Version switching evidence must keep UI, Effect API, CSS, i18n JSON, and MF
161
165
  manifest markers in lockstep for the same vertical version.
package/dist/index.js CHANGED
@@ -610,6 +610,14 @@ const modernPackageNames = [
610
610
  '@modern-js/plugin-tanstack',
611
611
  '@modern-js/runtime'
612
612
  ];
613
+ function sortJsonValue(value) {
614
+ if (Array.isArray(value)) return value.map(sortJsonValue);
615
+ if (null !== value && 'object' == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entry])=>[
616
+ key,
617
+ sortJsonValue(entry)
618
+ ]));
619
+ return value;
620
+ }
613
621
  const ULTRAMODERN_WORKSPACE_FLAG = '--ultramodern-workspace';
614
622
  function isRecord(value) {
615
623
  return null !== value && 'object' == typeof value && !Array.isArray(value);
@@ -973,10 +981,10 @@ function createRootPackageJson(scope, packageSource, remotes = []) {
973
981
  `pnpm --filter ${ultramodern_workspace_packageName(scope, remote.packageSuffix)} dev`
974
982
  ])),
975
983
  build: `${remoteBuildPrefix}pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types`,
976
- format: 'oxfmt .',
977
- 'format:check': 'oxfmt --check .',
978
- lint: 'oxlint .',
979
- 'lint:fix': 'oxlint . --fix',
984
+ format: "oxfmt . '!repos/**'",
985
+ 'format:check': "oxfmt --check . '!repos/**'",
986
+ lint: 'oxlint apps/*/src verticals/*/src packages/*/src --ignore-pattern "**/modern-tanstack/**"',
987
+ 'lint:fix': 'oxlint apps/*/src verticals/*/src packages/*/src --ignore-pattern "**/modern-tanstack/**" --fix',
980
988
  typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
981
989
  'cloudflare:build': `${remoteCloudflareBuildPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types`,
982
990
  'cloudflare:deploy': `${remoteCloudflareDeployPrefix}pnpm --filter "./apps/shell-super-app" run cloudflare:deploy`,
@@ -987,8 +995,9 @@ function createRootPackageJson(scope, packageSource, remotes = []) {
987
995
  'agents:refs:check': "node ./scripts/setup-agent-reference-repos.mjs --check",
988
996
  'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
989
997
  'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
990
- 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",
991
- check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:check'
998
+ 'ultramodern:i18n-boundaries': "node ./scripts/check-ultramodern-i18n-boundaries.mjs",
999
+ postinstall: "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)",
1000
+ check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:i18n-boundaries && pnpm ultramodern:check'
992
1001
  },
993
1002
  engines: {
994
1003
  node: '>=20',
@@ -1160,7 +1169,7 @@ function createAppPackage(scope, app, packageSource, enableTailwind, remotes = [
1160
1169
  dev: 'modern dev',
1161
1170
  build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
1162
1171
  'cloudflare:build': 'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern build && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
1163
- 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
1172
+ 'cloudflare:deploy': 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json',
1164
1173
  'cloudflare:preview': 'pnpm run cloudflare:build && wrangler dev --config .output/wrangler.json',
1165
1174
  'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mjs --app ${app.id}`,
1166
1175
  serve: 'modern serve',
@@ -1325,6 +1334,7 @@ ${bffConfig} output: {
1325
1334
  i18nPlugin({
1326
1335
  backend: {
1327
1336
  enabled: true,
1337
+ loadPath: '/locales/{{lng}}/{{ns}}.json',
1328
1338
  },
1329
1339
  reactI18next: false,
1330
1340
  localeDetection: {
@@ -1365,9 +1375,7 @@ ${bffPluginEntry} moduleFederationPlugin(),
1365
1375
  ...(cloudflareDeployEnabled
1366
1376
  ? {
1367
1377
  deploy: {
1368
- target: 'cloudflare',
1369
1378
  worker: {
1370
- name: cloudflareWorkerName,
1371
1379
  ssr: true,
1372
1380
  },
1373
1381
  },
@@ -1377,7 +1385,7 @@ ${bffPluginEntry} moduleFederationPlugin(),
1377
1385
  port,
1378
1386
  publicDir: ['./locales', './assets'],
1379
1387
  ssr: {
1380
- mode: 'stream',
1388
+ mode: 'string',
1381
1389
  moduleFederationAppSSR: true,
1382
1390
  },
1383
1391
  },
@@ -1547,10 +1555,10 @@ function createBuildMarker(scope, app) {
1547
1555
  function createUltramodernBuildModule(scope, app) {
1548
1556
  return `export const ultramodernVerticalIdentity = {
1549
1557
  appId: '${app.id}',
1550
- packageName: '${ultramodern_workspace_packageName(scope, app.packageSuffix)}',
1551
- version: '0.1.0',
1552
1558
  build: '${createBuildMarker(scope, app)}',
1553
1559
  deployProfile: 'cloudflare-ssr-mf-effect-v1',
1560
+ packageName: '${ultramodern_workspace_packageName(scope, app.packageSuffix)}',
1561
+ version: '0.1.0',
1554
1562
  } as const;
1555
1563
 
1556
1564
  export const ultramodernUiMarker = {
@@ -1791,8 +1799,8 @@ function createLocalisedUrlsMap(app) {
1791
1799
  }));
1792
1800
  }
1793
1801
  function createRouteMetadataModule(app) {
1794
- const routes = createRouteOwnedI18nPaths(app);
1795
- const localisedUrls = createLocalisedUrlsMap(app);
1802
+ const routes = sortJsonValue(createRouteOwnedI18nPaths(app));
1803
+ const localisedUrls = sortJsonValue(createLocalisedUrlsMap(app));
1796
1804
  const namespace = appI18nNamespace(app);
1797
1805
  return `export const ultramodernRouteNamespace = '${namespace}' as const;
1798
1806
 
@@ -1801,10 +1809,10 @@ export const ultramodernRouteMetadata = ${JSON.stringify(routes, null, 2)} as co
1801
1809
  export const ultramodernLocalisedUrls = ${JSON.stringify(localisedUrls, null, 2)} as const;
1802
1810
 
1803
1811
  export const ultramodernRouteConfig = {
1804
- source: 'route-owned',
1805
- namespace: ultramodernRouteNamespace,
1806
1812
  localisedUrls: ultramodernLocalisedUrls,
1813
+ namespace: ultramodernRouteNamespace,
1807
1814
  routes: ultramodernRouteMetadata,
1815
+ source: 'route-owned',
1808
1816
  } as const;
1809
1817
  `;
1810
1818
  }
@@ -1830,37 +1838,40 @@ function createRouteAliasPage(canonicalPath) {
1830
1838
  }
1831
1839
  function createBoundaryDebugMetadata(scope, remotes = []) {
1832
1840
  return {
1833
- schemaVersion: 1,
1834
1841
  appId: shellApp.id,
1835
1842
  boundaries: [
1836
1843
  shellApp,
1837
1844
  ...remotes
1838
1845
  ].map((app)=>({
1839
1846
  appId: app.id,
1847
+ label: app.displayName,
1840
1848
  mfName: app.mfName,
1841
- packageName: ultramodern_workspace_packageName(scope, app.packageSuffix),
1842
- role: 'shell' === app.kind ? 'host' : 'vertical',
1843
1849
  ownerTeam: app.ownership.team,
1844
- label: app.displayName
1845
- }))
1850
+ packageName: ultramodern_workspace_packageName(scope, app.packageSuffix),
1851
+ role: 'shell' === app.kind ? 'host' : 'vertical'
1852
+ })),
1853
+ schemaVersion: 1
1846
1854
  };
1847
1855
  }
1848
1856
  function createAppEnvDts(app, remotes = []) {
1849
1857
  const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
1850
1858
  const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
1851
1859
  return `declare module '${moduleName}' {
1852
- const Component: import('react').ComponentType<Record<string, never>>;
1860
+ const Component: React.ComponentType<Record<string, never>>;
1853
1861
  export default Component;
1854
1862
  }
1855
1863
  `;
1856
1864
  })).join('\n');
1857
- return `/// <reference types='@modern-js/app-tools/types' />
1865
+ const reactTypeReference = remoteModuleDeclarations ? "/// <reference types='react' />\n" : '';
1866
+ const siteUrlDeclaration = 'declare const ULTRAMODERN_SITE_URL: string;';
1867
+ return `${reactTypeReference}/// <reference types='@modern-js/app-tools/types' />
1858
1868
 
1859
- declare const ULTRAMODERN_SITE_URL: string;
1869
+ ${siteUrlDeclaration}
1860
1870
  declare module '*.svg' {
1861
1871
  const url: string;
1862
1872
  export default url;
1863
1873
  }
1874
+ declare module '*.css';
1864
1875
  ${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
1865
1876
  }
1866
1877
  function createAppRuntimeConfig(app, scope, remotes = []) {
@@ -1889,10 +1900,10 @@ export default defineRuntimeConfig({
1889
1900
  supportedLngs: ['en', 'cs'],
1890
1901
  },
1891
1902
  },
1903
+ ${pluginsConfig}
1892
1904
  router: {
1893
1905
  framework: 'tanstack',
1894
1906
  },
1895
- ${pluginsConfig}
1896
1907
  });
1897
1908
  `;
1898
1909
  }
@@ -1953,7 +1964,7 @@ function createTw(prefix) {
1953
1964
  function workspaceAssetsForApp(_app) {
1954
1965
  return {};
1955
1966
  }
1956
- function createLocalizedHeadComponent() {
1967
+ function createLocalizedHeadComponent(includeLocationSuffix = true) {
1957
1968
  return `const fallbackLanguage = 'en';
1958
1969
  const supportedLanguages = ['en', 'cs'] as const;
1959
1970
  type SupportedLanguage = (typeof supportedLanguages)[number];
@@ -1967,7 +1978,7 @@ const isSupportedLanguage = (value: string): value is SupportedLanguage =>
1967
1978
  supportedLanguages.includes(value as SupportedLanguage);
1968
1979
 
1969
1980
  const normalisePath = (pathname: string) => {
1970
- const normalised = pathname.replace(/\\/+$/u, '').replace(/\\/+/gu, '/');
1981
+ const normalised = pathname.replaceAll(/\\/+/gu, '/').replace(/\\/+$/u, '');
1971
1982
  return normalised.length > 0 ? normalised : '/';
1972
1983
  };
1973
1984
 
@@ -1980,7 +1991,7 @@ const stripLanguagePrefix = (pathname: string) => {
1980
1991
  };
1981
1992
 
1982
1993
  const escapeRegExp = (value: string) =>
1983
- value.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
1994
+ value.replaceAll(/[.*+?^\${}()|[\\]\\\\]/gu, '\\\\$&');
1984
1995
 
1985
1996
  const paramName = (segment: string) => segment.slice(1).replace(/\\?$/u, '');
1986
1997
 
@@ -1997,16 +2008,19 @@ const matchPattern = (pathname: string, pattern: string) => {
1997
2008
  return \`/\${escapeRegExp(segment)}\`;
1998
2009
  })
1999
2010
  .join('');
2000
- const match = new RegExp(\`^\${source || '/'}$\`).exec(normalisePath(pathname));
2011
+ const match = new RegExp(\`^\${source || '/'}$\`, 'u').exec(
2012
+ normalisePath(pathname),
2013
+ );
2001
2014
 
2002
- if (!match) {
2003
- return undefined;
2015
+ if (match === null) {
2016
+ return;
2004
2017
  }
2005
2018
 
2006
- return names.reduce<Record<string, string>>((params, name, index) => {
2019
+ const params: Record<string, string> = {};
2020
+ for (const [index, name] of names.entries()) {
2007
2021
  params[name] = decodeURIComponent(match[index + 1] ?? '');
2008
- return params;
2009
- }, {});
2022
+ }
2023
+ return params;
2010
2024
  };
2011
2025
 
2012
2026
  const buildPath = (pattern: string, params: Record<string, string>) => {
@@ -2018,7 +2032,9 @@ const buildPath = (pattern: string, params: Record<string, string>) => {
2018
2032
  return segment;
2019
2033
  }
2020
2034
  const value = params[paramName(segment)];
2021
- return value ? encodeURIComponent(value) : '';
2035
+ return value !== undefined && value.length > 0
2036
+ ? encodeURIComponent(value)
2037
+ : '';
2022
2038
  })
2023
2039
  .filter(Boolean)
2024
2040
  .join('/');
@@ -2034,16 +2050,17 @@ const resolveLocalisedPath = (
2034
2050
 
2035
2051
  for (const entry of Object.values(localisedUrls)) {
2036
2052
  const targetPattern = entry[targetLanguage];
2037
- if (!targetPattern) {
2053
+ if (targetPattern === undefined) {
2038
2054
  continue;
2039
2055
  }
2040
2056
 
2041
2057
  for (const language of supportedLanguages) {
2042
2058
  const sourcePattern = entry[language];
2043
- const params = sourcePattern
2044
- ? matchPattern(pathWithoutLanguage, sourcePattern)
2045
- : undefined;
2046
- if (params) {
2059
+ const params =
2060
+ sourcePattern === undefined
2061
+ ? undefined
2062
+ : matchPattern(pathWithoutLanguage, sourcePattern);
2063
+ if (params !== undefined) {
2047
2064
  return buildPath(targetPattern, params);
2048
2065
  }
2049
2066
  }
@@ -2062,21 +2079,22 @@ const absoluteUrl = (pathname: string) => {
2062
2079
  return \`\${origin}\${pathname}\`;
2063
2080
  };
2064
2081
 
2065
- const locationSuffix = (location: {
2082
+ ${includeLocationSuffix ? `const locationSuffix = (location: {
2066
2083
  hash?: unknown;
2067
2084
  search?: unknown;
2068
2085
  searchStr?: unknown;
2069
2086
  }) => {
2070
- const locationSearch =
2071
- typeof location.searchStr === 'string'
2072
- ? location.searchStr
2073
- : typeof location.search === 'string'
2074
- ? location.search
2075
- : '';
2087
+ let locationSearch = '';
2088
+ if (typeof location.searchStr === 'string') {
2089
+ locationSearch = location.searchStr;
2090
+ } else if (typeof location.search === 'string') {
2091
+ locationSearch = location.search;
2092
+ }
2076
2093
  const locationHash = typeof location.hash === 'string' ? location.hash : '';
2077
2094
 
2078
2095
  return \`\${locationSearch}\${locationHash}\`;
2079
2096
  };
2097
+ ` : ''}
2080
2098
 
2081
2099
  const LocalizedHead = () => {
2082
2100
  const location = useLocation();
@@ -2114,7 +2132,7 @@ import { VerticalShowcase } from '../vertical-components';
2114
2132
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2115
2133
  import { ultramodernUiMarker } from '../../ultramodern-build';
2116
2134
 
2117
- ${createLocalizedHeadComponent()}
2135
+ ${createLocalizedHeadComponent(false)}
2118
2136
  export default function ShellHome() {
2119
2137
  const { i18nInstance } = useModernI18n();
2120
2138
  const t = i18nInstance['t'].bind(i18nInstance);
@@ -2172,9 +2190,9 @@ import { ultramodernLocalisedUrls } from './ultramodern-route-metadata';
2172
2190
  const supportedLanguages = ['en', 'cs'] as const;
2173
2191
  type SupportedLanguage = (typeof supportedLanguages)[number];
2174
2192
 
2175
- type ShellFrameProps = {
2193
+ interface ShellFrameProps {
2176
2194
  children: ReactNode;
2177
- };
2195
+ }
2178
2196
 
2179
2197
  const localisedUrls = ultramodernLocalisedUrls as Record<
2180
2198
  string,
@@ -2185,7 +2203,7 @@ const isSupportedLanguage = (value: string): value is SupportedLanguage =>
2185
2203
  supportedLanguages.includes(value as SupportedLanguage);
2186
2204
 
2187
2205
  const normalisePath = (pathname: string) => {
2188
- const normalised = pathname.replace(/\\/+$/u, '').replace(/\\/+/gu, '/');
2206
+ const normalised = pathname.replaceAll(/\\/+/gu, '/').replace(/\\/+$/u, '');
2189
2207
  return normalised.length > 0 ? normalised : '/';
2190
2208
  };
2191
2209
 
@@ -2198,7 +2216,7 @@ const stripLanguagePrefix = (pathname: string) => {
2198
2216
  };
2199
2217
 
2200
2218
  const escapeRegExp = (value: string) =>
2201
- value.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
2219
+ value.replaceAll(/[.*+?^\${}()|[\\]\\\\]/gu, '\\\\$&');
2202
2220
 
2203
2221
  const paramName = (segment: string) => segment.slice(1).replace(/\\?$/u, '');
2204
2222
 
@@ -2215,16 +2233,19 @@ const matchPattern = (pathname: string, pattern: string) => {
2215
2233
  return \`/\${escapeRegExp(segment)}\`;
2216
2234
  })
2217
2235
  .join('');
2218
- const match = new RegExp(\`^\${source || '/'}$\`).exec(normalisePath(pathname));
2236
+ const match = new RegExp(\`^\${source || '/'}$\`, 'u').exec(
2237
+ normalisePath(pathname),
2238
+ );
2219
2239
 
2220
- if (!match) {
2221
- return undefined;
2240
+ if (match === null) {
2241
+ return;
2222
2242
  }
2223
2243
 
2224
- return names.reduce<Record<string, string>>((params, name, index) => {
2244
+ const params: Record<string, string> = {};
2245
+ for (const [index, name] of names.entries()) {
2225
2246
  params[name] = decodeURIComponent(match[index + 1] ?? '');
2226
- return params;
2227
- }, {});
2247
+ }
2248
+ return params;
2228
2249
  };
2229
2250
 
2230
2251
  const buildPath = (pattern: string, params: Record<string, string>) => {
@@ -2236,7 +2257,9 @@ const buildPath = (pattern: string, params: Record<string, string>) => {
2236
2257
  return segment;
2237
2258
  }
2238
2259
  const value = params[paramName(segment)];
2239
- return value ? encodeURIComponent(value) : '';
2260
+ return value !== undefined && value.length > 0
2261
+ ? encodeURIComponent(value)
2262
+ : '';
2240
2263
  })
2241
2264
  .filter(Boolean)
2242
2265
  .join('/');
@@ -2252,16 +2275,17 @@ const resolveLocalisedPath = (
2252
2275
 
2253
2276
  for (const entry of Object.values(localisedUrls)) {
2254
2277
  const targetPattern = entry[targetLanguage];
2255
- if (!targetPattern) {
2278
+ if (targetPattern === undefined) {
2256
2279
  continue;
2257
2280
  }
2258
2281
 
2259
2282
  for (const language of supportedLanguages) {
2260
2283
  const sourcePattern = entry[language];
2261
- const params = sourcePattern
2262
- ? matchPattern(pathWithoutLanguage, sourcePattern)
2263
- : undefined;
2264
- if (params) {
2284
+ const params =
2285
+ sourcePattern === undefined
2286
+ ? undefined
2287
+ : matchPattern(pathWithoutLanguage, sourcePattern);
2288
+ if (params !== undefined) {
2265
2289
  return buildPath(targetPattern, params);
2266
2290
  }
2267
2291
  }
@@ -2280,12 +2304,12 @@ const locationSuffix = (location: {
2280
2304
  search?: unknown;
2281
2305
  searchStr?: unknown;
2282
2306
  }) => {
2283
- const locationSearch =
2284
- typeof location.searchStr === 'string'
2285
- ? location.searchStr
2286
- : typeof location.search === 'string'
2287
- ? location.search
2288
- : '';
2307
+ let locationSearch = '';
2308
+ if (typeof location.searchStr === 'string') {
2309
+ locationSearch = location.searchStr;
2310
+ } else if (typeof location.search === 'string') {
2311
+ locationSearch = location.search;
2312
+ }
2289
2313
  const locationHash = typeof location.hash === 'string' ? location.hash : '';
2290
2314
 
2291
2315
  return \`\${locationSearch}\${locationHash}\`;
@@ -2351,21 +2375,19 @@ function createShellRemoteComponents(scope, remotes = []) {
2351
2375
  const remoteCount = String(widgetRemotes.length);
2352
2376
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
2353
2377
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
2354
- import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
2378
+ import { Suspense, useEffect, useMemo, useState } from 'react';
2379
+ import type { ComponentType } from 'react';
2355
2380
  import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2356
2381
  ${serverImports}
2357
2382
 
2358
- type RemoteComponentModule = {
2383
+ interface RemoteComponentModule {
2359
2384
  default: ComponentType;
2360
- };
2385
+ }
2361
2386
 
2362
- const loadRemoteComponent = async (specifier: string) => {
2363
- const module = await loadRemote<RemoteComponentModule>(specifier);
2364
- if (!module) {
2365
- throw new Error(\`Remote module unavailable: \${specifier}\`);
2366
- }
2367
- return module;
2368
- };
2387
+ const widgetCount = Number('${remoteCount}');
2388
+
2389
+ const loadRemoteComponent = (specifier: string) =>
2390
+ loadRemote<RemoteComponentModule>(specifier) as Promise<RemoteComponentModule>;
2369
2391
 
2370
2392
  const remoteFallback =
2371
2393
  ({ error }: { error: Error }) => {
@@ -2374,11 +2396,9 @@ const remoteFallback =
2374
2396
  return <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>{t('shell.remoteUnavailable')}</div>;
2375
2397
  };
2376
2398
 
2377
- const createHydratedRemote = (
2378
- ServerComponent: ComponentType,
2379
- specifier: string,
2380
- ) => {
2381
- return function HydratedRemote() {
2399
+ const createHydratedRemote =
2400
+ (ServerComponent: ComponentType, specifier: string) =>
2401
+ function HydratedRemote() {
2382
2402
  const [hydrated, setHydrated] = useState(false);
2383
2403
 
2384
2404
  useEffect(() => {
@@ -2387,11 +2407,11 @@ const createHydratedRemote = (
2387
2407
 
2388
2408
  const FederatedComponent = useMemo(() => {
2389
2409
  if (!hydrated) {
2390
- return undefined;
2410
+ return null;
2391
2411
  }
2392
2412
  const instance = getInstance();
2393
- if (!instance) {
2394
- return undefined;
2413
+ if (instance === null || instance === undefined) {
2414
+ return null;
2395
2415
  }
2396
2416
  return createLazyComponent({
2397
2417
  export: 'default',
@@ -2402,7 +2422,7 @@ const createHydratedRemote = (
2402
2422
  });
2403
2423
  }, [hydrated]);
2404
2424
 
2405
- if (!FederatedComponent) {
2425
+ if (FederatedComponent === null) {
2406
2426
  return <ServerComponent />;
2407
2427
  }
2408
2428
 
@@ -2412,11 +2432,10 @@ const createHydratedRemote = (
2412
2432
  </Suspense>
2413
2433
  );
2414
2434
  };
2415
- };
2416
2435
 
2417
2436
  ${hydratedExports}
2418
2437
 
2419
- export function Header() {
2438
+ export const Header = () => {
2420
2439
  const { i18nInstance } = useModernI18n();
2421
2440
  const t = i18nInstance['t'].bind(i18nInstance);
2422
2441
 
@@ -2425,24 +2444,24 @@ export function Header() {
2425
2444
  <I18nLink className="${tw('whitespace-nowrap text-xl font-black tracking-normal text-stone-950 no-underline')}" to="/">{t('shell.title')}</I18nLink>
2426
2445
  </header>
2427
2446
  );
2428
- }
2447
+ };
2429
2448
 
2430
- export function StatusBadge() {
2449
+ export const StatusBadge = () => {
2431
2450
  const { i18nInstance } = useModernI18n();
2432
2451
  const t = i18nInstance['t'].bind(i18nInstance);
2433
2452
 
2434
2453
  return (
2435
2454
  <span className="${tw('inline-flex h-10 shrink-0 items-center justify-center rounded-full border border-stone-900/15 bg-white px-4 text-sm font-extrabold text-stone-950 shadow-lg shadow-stone-900/5')}">
2436
- {${remoteCount}} {t('shell.hero.cardOneKicker')}
2455
+ {widgetCount} {t('shell.hero.cardOneKicker')}
2437
2456
  </span>
2438
2457
  );
2439
- }
2458
+ };
2440
2459
 
2441
- export function VerticalShowcase() {
2460
+ export const VerticalShowcase = () => {
2442
2461
  const { i18nInstance } = useModernI18n();
2443
2462
  const t = i18nInstance['t'].bind(i18nInstance);
2444
2463
 
2445
- if (${remoteCount} === 0) {
2464
+ if (widgetCount === 0) {
2446
2465
  return (
2447
2466
  <section className="${tw('mx-auto mt-12 max-w-7xl rounded-2xl bg-white/90 p-6 shadow-xl shadow-stone-900/10')}">
2448
2467
  <p className="${tw('text-lg font-bold text-stone-700')}">{t('shell.hero.empty')}</p>
@@ -2457,39 +2476,50 @@ ${showcaseItems}
2457
2476
  </div>
2458
2477
  </section>
2459
2478
  );
2460
- }
2479
+ };
2461
2480
  `;
2462
2481
  }
2463
2482
  function createRemotePage(app) {
2464
2483
  const tw = createTw(tailwindPrefixForApp(app));
2484
+ const listEffectItems = `list${toPascalCase(effectApiStem(app))}`;
2465
2485
  const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2466
2486
  import { Helmet } from '@modern-js/runtime/head';
2467
2487
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2468
2488
  import { useEffect, useState } from 'react';
2489
+ import {
2490
+ Effect,
2491
+ ${listEffectItems},
2492
+ runEffectRequest,
2493
+ } from '../../effect/${effectApiStem(app)}-client';
2469
2494
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2470
2495
  import { ultramodernUiMarker } from '../../ultramodern-build';
2471
2496
  ` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { Helmet } from '@modern-js/runtime/head';\nimport { useLocation } from '@modern-js/plugin-tanstack/runtime';\nimport { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
2472
2497
  const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
2473
2498
 
2474
2499
  useEffect(() => {
2475
- void fetch('${effectApiPrefix(app)}/effect/${effectApiStem(app)}?limit=1', {
2476
- headers: {
2477
- accept: 'application/json',
2478
- },
2479
- })
2480
- .then(response => {
2481
- if (!response.ok) {
2482
- throw new Error(\`Effect BFF request failed: \${response.status}\`);
2483
- }
2500
+ let cancelled = false;
2501
+ void runEffectRequest(
2502
+ ${listEffectItems}({ limit: 1 }).pipe(
2503
+ Effect.match({
2504
+ onFailure: () => {
2505
+ if (cancelled) {
2506
+ return;
2507
+ }
2508
+ setEffectApiStatus('unavailable');
2509
+ },
2510
+ onSuccess: data => {
2511
+ if (cancelled) {
2512
+ return;
2513
+ }
2514
+ setEffectApiStatus(data.items.at(0)?.title ?? 'empty');
2515
+ },
2516
+ }),
2517
+ ),
2518
+ );
2484
2519
 
2485
- return response.json() as Promise<{ items?: Array<{ title?: string }> }>;
2486
- })
2487
- .then(data => {
2488
- setEffectApiStatus(data.items[0]?.title ?? 'empty');
2489
- })
2490
- .catch(() => {
2491
- setEffectApiStatus('unavailable');
2492
- });
2520
+ return () => {
2521
+ cancelled = true;
2522
+ };
2493
2523
  }, []);
2494
2524
 
2495
2525
  ` : '';
@@ -2518,7 +2548,7 @@ ${effectBffState} return (
2518
2548
  ))}
2519
2549
  </nav>
2520
2550
  <h1 className="${tw('mt-10 text-5xl font-black')}">{t('${app.domain}.title')}</h1>
2521
- <p className="${tw('mt-3 text-lg text-stone-600')}" data-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
2551
+ <p className="${tw('mt-3 text-lg text-stone-600')}" data-modern-mf-role="${app.kind}">{t('${app.domain}.role')}</p>
2522
2552
  <p className="${tw('sr-only')}" data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2523
2553
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2524
2554
  </p>
@@ -2554,7 +2584,7 @@ export default function ${toPascalCase(domain)}Route() {
2554
2584
  const t = i18nInstance['t'].bind(i18nInstance);
2555
2585
 
2556
2586
  return (
2557
- <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}" data-mf-expose="./Route">
2587
+ <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="./Route">
2558
2588
  <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2559
2589
  <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.routeSurface')}</p>
2560
2590
  </section>
@@ -2573,7 +2603,7 @@ export default function ${componentName}() {
2573
2603
  const t = i18nInstance['t'].bind(i18nInstance);
2574
2604
 
2575
2605
  return (
2576
- <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-mf-remote="${app.id}">
2606
+ <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="./Widget">
2577
2607
  <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2578
2608
  <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.widgetBody')}</p>
2579
2609
  </section>
@@ -2672,7 +2702,7 @@ export default function ${componentName}() {
2672
2702
 
2673
2703
  return (
2674
2704
  <>
2675
- <section className="${tw('mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2705
+ <section className="${tw('mx-auto mt-10 grid max-w-7xl items-center gap-8 md:grid-cols-[1fr_0.95fr] lg:gap-14')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2676
2706
  <div className="${tw('rounded-3xl border-[18px] border-amber-200 bg-white/90 p-8 shadow-2xl shadow-stone-900/15')}">
2677
2707
  <p className="${tw('text-xs font-black uppercase tracking-[0.18em] text-emerald-800')}">{t('records.record.lifecycle')}</p>
2678
2708
  <dl className="${tw('mt-6 grid gap-4')}">
@@ -2741,7 +2771,7 @@ export default function ${componentName}() {
2741
2771
  const queue = useActionQueue();
2742
2772
 
2743
2773
  return (
2744
- <section className="${tw('mx-auto mt-10 max-w-7xl')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2774
+ <section className="${tw('mx-auto mt-10 max-w-7xl')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2745
2775
  <h1 className="${tw('text-5xl font-black leading-none tracking-normal text-stone-950 md:text-7xl')}">{t('actions.queue.title')}</h1>
2746
2776
  <div className="${tw('mt-8 rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}">
2747
2777
  {queue.lines.length === 0 ? (
@@ -2783,7 +2813,7 @@ export default function ${componentName}() {
2783
2813
  const t = i18nInstance['t'].bind(i18nInstance);
2784
2814
 
2785
2815
  return (
2786
- <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}" data-mf-remote="${app.id}">
2816
+ <section className="${tw('rounded-2xl bg-white/90 p-5 shadow-xl shadow-stone-900/10')}" data-modern-boundary-id="${app.mfName}" data-modern-mf-expose="${expose}">
2787
2817
  <h2 className="${tw('text-2xl font-black')}">{t('${domain}.title')}</h2>
2788
2818
  <p className="${tw('mt-2 text-stone-600')}">{t('${domain}.federatedSurface')}</p>
2789
2819
  </section>
@@ -2795,22 +2825,18 @@ function createRecordsRemoteComponents(scope, app) {
2795
2825
  const tw = createTw(tailwindPrefixForApp(app));
2796
2826
  return `import { createLazyComponent } from '@module-federation/modern-js-v3/react';
2797
2827
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
2798
- import { Suspense, useEffect, useMemo, useState, type ComponentType } from 'react';
2828
+ import { Suspense, useEffect, useMemo, useState } from 'react';
2829
+ import type { ComponentType } from 'react';
2799
2830
  import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2800
2831
  import HighlightsServer from '${ultramodern_workspace_packageName(scope, 'workspace')}/Highlights';
2801
2832
  import StartActionServer from '${ultramodern_workspace_packageName(scope, 'actions')}/StartAction';
2802
2833
 
2803
- type RemoteComponentModule = {
2834
+ interface RemoteComponentModule {
2804
2835
  default: ComponentType;
2805
- };
2836
+ }
2806
2837
 
2807
- const loadRemoteComponent = async (specifier: string) => {
2808
- const module = await loadRemote<RemoteComponentModule>(specifier);
2809
- if (!module) {
2810
- throw new Error(\`Remote module unavailable: \${specifier}\`);
2811
- }
2812
- return module;
2813
- };
2838
+ const loadRemoteComponent = (specifier: string) =>
2839
+ loadRemote<RemoteComponentModule>(specifier) as Promise<RemoteComponentModule>;
2814
2840
 
2815
2841
  const remoteFallback =
2816
2842
  ({ error }: { error: Error }) => {
@@ -2819,11 +2845,9 @@ const remoteFallback =
2819
2845
  return <div className="${tw('rounded-xl border border-red-900/20 bg-red-50 px-4 py-3 text-sm font-semibold text-red-900')}" data-remote-error={error.name}>{t('records.remoteUnavailable')}</div>;
2820
2846
  };
2821
2847
 
2822
- const createHydratedRemote = (
2823
- ServerComponent: ComponentType,
2824
- specifier: string,
2825
- ) => {
2826
- return function HydratedRemote() {
2848
+ const createHydratedRemote =
2849
+ (ServerComponent: ComponentType, specifier: string) =>
2850
+ function HydratedRemote() {
2827
2851
  const [hydrated, setHydrated] = useState(false);
2828
2852
 
2829
2853
  useEffect(() => {
@@ -2832,11 +2856,11 @@ const createHydratedRemote = (
2832
2856
 
2833
2857
  const FederatedComponent = useMemo(() => {
2834
2858
  if (!hydrated) {
2835
- return undefined;
2859
+ return null;
2836
2860
  }
2837
2861
  const instance = getInstance();
2838
- if (!instance) {
2839
- return undefined;
2862
+ if (instance === null || instance === undefined) {
2863
+ return null;
2840
2864
  }
2841
2865
  return createLazyComponent({
2842
2866
  export: 'default',
@@ -2847,7 +2871,7 @@ const createHydratedRemote = (
2847
2871
  });
2848
2872
  }, [hydrated]);
2849
2873
 
2850
- if (!FederatedComponent) {
2874
+ if (FederatedComponent === null) {
2851
2875
  return <ServerComponent />;
2852
2876
  }
2853
2877
 
@@ -2857,7 +2881,6 @@ const createHydratedRemote = (
2857
2881
  </Suspense>
2858
2882
  );
2859
2883
  };
2860
- };
2861
2884
 
2862
2885
  export const Highlights = createHydratedRemote(HighlightsServer, 'workspace/Highlights');
2863
2886
  export const StartAction = createHydratedRemote(StartActionServer, 'actions/StartAction');
@@ -2960,7 +2983,7 @@ const generatedLocaleResources = {
2960
2983
  },
2961
2984
  shell: {
2962
2985
  boundaries: {
2963
- toggle: 'zobrazit hranice verticalů'
2986
+ toggle: 'zobrazit hranice týmů'
2964
2987
  },
2965
2988
  hero: {
2966
2989
  cardOne: 'Přidejte první business vertical příkazem create <domain> --vertical, až ho opravdu potřebujete.',
@@ -3069,7 +3092,7 @@ const generatedLocaleResources = {
3069
3092
  },
3070
3093
  shell: {
3071
3094
  boundaries: {
3072
- toggle: 'show vertical boundaries'
3095
+ toggle: 'show team boundaries'
3073
3096
  },
3074
3097
  hero: {
3075
3098
  cardOne: 'Add the first business vertical with create <domain> --vertical when the product needs one.',
@@ -3454,8 +3477,7 @@ const ${groupName}Items = [
3454
3477
  },
3455
3478
  ];
3456
3479
 
3457
- const operationAttributes = (operationContext: OperationContext) => {
3458
- return {
3480
+ const operationAttributes = (operationContext: OperationContext) => ({
3459
3481
  'modernjs.operation.id': operationContext.operationId,
3460
3482
  'modernjs.operation.method': operationContext.method,
3461
3483
  'modernjs.operation.route': operationContext.routePath,
@@ -3463,8 +3485,7 @@ const operationAttributes = (operationContext: OperationContext) => {
3463
3485
  ...(typeof operationContext.traceId === 'string'
3464
3486
  ? { 'modernjs.trace.id': operationContext.traceId }
3465
3487
  : {}),
3466
- };
3467
- };
3488
+ });
3468
3489
 
3469
3490
  const ${groupName}Layer = HttpApiBuilder.group(
3470
3491
  ${apiExport},
@@ -3555,6 +3576,7 @@ function createEffectClient(service, contractImportPath) {
3555
3576
  const getName = `get${toPascalCase(singular)}`;
3556
3577
  const createName = `create${toPascalCase(singular)}`;
3557
3578
  return `import {
3579
+ Effect,
3558
3580
  makeEffectHttpApiClient,
3559
3581
  runEffectRequest,
3560
3582
  } from '@modern-js/plugin-bff/effect-client';
@@ -3562,85 +3584,74 @@ import {
3562
3584
  ${contractExport}ApiContract,
3563
3585
  ${apiExport},
3564
3586
  ${groupName}OperationContexts,
3565
- type OperationContext,
3566
3587
  } from '${contractImportPath}';
3588
+ import type { OperationContext } from '${contractImportPath}';
3589
+
3590
+ export { Effect, runEffectRequest };
3567
3591
 
3568
- export type ${clientOptionsName} = {
3592
+ export interface ${clientOptionsName} {
3569
3593
  baseUrl?: string | URL;
3570
3594
  locale?: string;
3571
3595
  operationContext?: OperationContext;
3572
3596
  traceparent?: string;
3573
- };
3597
+ }
3574
3598
 
3575
- export function ${createClientName}(
3599
+ export const ${createClientName} = (
3576
3600
  options: ${clientOptionsName} = {},
3577
- ) {
3578
- return makeEffectHttpApiClient(${apiExport}, {
3601
+ ) =>
3602
+ makeEffectHttpApiClient(${apiExport}, {
3579
3603
  baseUrl: options.baseUrl ?? ${contractExport}ApiContract.apiPrefix,
3580
3604
  });
3581
- }
3582
3605
 
3583
- export function ${listName}(
3606
+ export const ${listName} = (
3584
3607
  options: ${clientOptionsName} & { limit?: number } = {},
3585
- ) {
3586
- return runEffectRequest(
3587
- ${createClientName}({
3588
- ...options,
3589
- operationContext:
3590
- options.operationContext ?? ${groupName}OperationContexts.list,
3591
- }),
3592
- ).then(client =>
3593
- runEffectRequest(
3608
+ ) =>
3609
+ ${createClientName}({
3610
+ ...options,
3611
+ operationContext:
3612
+ options.operationContext ?? ${groupName}OperationContexts.list,
3613
+ }).pipe(
3614
+ Effect.flatMap(client =>
3594
3615
  client.${groupName}.list({ query: { limit: options.limit } }),
3595
3616
  ),
3596
3617
  );
3597
- }
3598
3618
 
3599
- export function ${readinessName}(
3619
+ export const ${readinessName} = (
3600
3620
  options: ${clientOptionsName} = {},
3601
- ) {
3602
- return runEffectRequest(
3603
- ${createClientName}({
3604
- ...options,
3605
- operationContext:
3606
- options.operationContext ?? ${groupName}OperationContexts.readiness,
3607
- }),
3608
- ).then(client =>
3609
- runEffectRequest(client.${groupName}.readiness({})),
3621
+ ) =>
3622
+ ${createClientName}({
3623
+ ...options,
3624
+ operationContext:
3625
+ options.operationContext ?? ${groupName}OperationContexts.readiness,
3626
+ }).pipe(
3627
+ Effect.flatMap(client => client.${groupName}.readiness({})),
3610
3628
  );
3611
- }
3612
3629
 
3613
- export function ${getName}(
3630
+ export const ${getName} = (
3614
3631
  id: string,
3615
3632
  options: ${clientOptionsName} = {},
3616
- ) {
3617
- return runEffectRequest(
3618
- ${createClientName}({
3619
- ...options,
3620
- operationContext:
3621
- options.operationContext ?? ${groupName}OperationContexts.get,
3622
- }),
3623
- ).then(client =>
3624
- runEffectRequest(client.${groupName}.get({ params: { id } })),
3633
+ ) =>
3634
+ ${createClientName}({
3635
+ ...options,
3636
+ operationContext:
3637
+ options.operationContext ?? ${groupName}OperationContexts.get,
3638
+ }).pipe(
3639
+ Effect.flatMap(client => client.${groupName}.get({ params: { id } })),
3625
3640
  );
3626
- }
3627
3641
 
3628
- export function ${createName}(
3642
+ export const ${createName} = (
3629
3643
  title: string,
3630
3644
  options: ${clientOptionsName} = {},
3631
- ) {
3632
- return runEffectRequest(
3633
- ${createClientName}({
3634
- ...options,
3635
- operationContext:
3636
- options.operationContext ?? ${groupName}OperationContexts.create,
3637
- }),
3638
- ).then(client =>
3639
- runEffectRequest(
3645
+ ) =>
3646
+ ${createClientName}({
3647
+ ...options,
3648
+ operationContext:
3649
+ options.operationContext ?? ${groupName}OperationContexts.create,
3650
+ }).pipe(
3651
+ Effect.flatMap(client =>
3640
3652
  client.${groupName}.create({ payload: { title } }),
3641
3653
  ),
3642
3654
  );
3643
- }
3644
3655
  `;
3645
3656
  }
3646
3657
  function createShellEffectClient(scope, remotes = []) {
@@ -3850,6 +3861,7 @@ function createTopology(scope, remotes = []) {
3850
3861
  validation: {
3851
3862
  script: "scripts/validate-ultramodern-workspace.mjs",
3852
3863
  commands: [
3864
+ 'pnpm ultramodern:i18n-boundaries',
3853
3865
  'pnpm ultramodern:check'
3854
3866
  ]
3855
3867
  }
@@ -4193,7 +4205,7 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
4193
4205
  }
4194
4206
  },
4195
4207
  ssr: {
4196
- mode: 'stream',
4208
+ mode: 'string',
4197
4209
  moduleFederationAppSSR: true
4198
4210
  },
4199
4211
  i18n: {
@@ -4415,6 +4427,7 @@ function createTemplateManifest(modernVersion, packageSource) {
4415
4427
  expectedCommands: [
4416
4428
  'mise install',
4417
4429
  'pnpm install',
4430
+ 'pnpm run ultramodern:i18n-boundaries',
4418
4431
  'pnpm run ultramodern:check'
4419
4432
  ]
4420
4433
  }
@@ -4492,6 +4505,197 @@ for (const appDir of appDirs) {
4492
4505
  }
4493
4506
  `;
4494
4507
  }
4508
+ function createWorkspaceI18nBoundaryValidationScript() {
4509
+ return `#!/usr/bin/env node
4510
+ import fs from 'node:fs';
4511
+ import path from 'node:path';
4512
+ import { fileURLToPath } from 'node:url';
4513
+
4514
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
4515
+ const sourceRoots = ['apps', 'verticals'];
4516
+ const languageConditionalPattern =
4517
+ /\\b(language|locale|lng|currentLanguage)\\s*={0,2}={1,2}\\s*['"][a-z-]+['"]\\s*\\?\\s*([^:;\\n]+)\\s*:\\s*([^;\\n})]+)/gu;
4518
+ const allowedLanguageConditionalBranches = new Set([
4519
+ "'page'",
4520
+ '"page"',
4521
+ 'undefined',
4522
+ 'null',
4523
+ 'true',
4524
+ 'false',
4525
+ ]);
4526
+ const visibleCopyAttributes = new Set([
4527
+ 'alt',
4528
+ 'aria-label',
4529
+ 'label',
4530
+ 'placeholder',
4531
+ 'title',
4532
+ ]);
4533
+
4534
+ function fail(message) {
4535
+ throw new Error(message);
4536
+ }
4537
+
4538
+ function walk(directory, files = []) {
4539
+ if (!fs.existsSync(directory)) {
4540
+ return files;
4541
+ }
4542
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
4543
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.output') {
4544
+ continue;
4545
+ }
4546
+ const entryPath = path.join(directory, entry.name);
4547
+ if (entry.isDirectory()) {
4548
+ walk(entryPath, files);
4549
+ } else {
4550
+ files.push(entryPath);
4551
+ }
4552
+ }
4553
+ return files;
4554
+ }
4555
+
4556
+ function relative(filePath) {
4557
+ return path.relative(root, filePath).replace(/\\\\/gu, '/');
4558
+ }
4559
+
4560
+ function isSourceFile(filePath) {
4561
+ return /\\.(?:ts|tsx|js|jsx)$/u.test(filePath);
4562
+ }
4563
+
4564
+ function isLocaleJson(filePath) {
4565
+ const normalized = relative(filePath);
4566
+ return /\\/locales\\/(en|cs)\\/[^/]+\\.json$/u.test(normalized);
4567
+ }
4568
+
4569
+ function readText(filePath) {
4570
+ return fs.readFileSync(filePath, 'utf8');
4571
+ }
4572
+
4573
+ function branchIsUserCopy(branch) {
4574
+ const value = branch.trim().replace(/,$/u, '');
4575
+ if (allowedLanguageConditionalBranches.has(value)) {
4576
+ return false;
4577
+ }
4578
+ return /^['"][^'"]{2,}['"]$/u.test(value);
4579
+ }
4580
+
4581
+ function checkRuntimeResources(filePath, text) {
4582
+ if (!relative(filePath).endsWith('/src/modern.runtime.ts')) {
4583
+ return;
4584
+ }
4585
+ if (/initOptions\\s*:\\s*\\{[\\s\\S]*?\\bresources\\s*:/u.test(text)) {
4586
+ fail(\`\${relative(filePath)} must not inline i18n resources in modern.runtime.ts; use locale JSON files.\`);
4587
+ }
4588
+ }
4589
+
4590
+ function checkLanguageConditionals(filePath, text) {
4591
+ for (const match of text.matchAll(languageConditionalPattern)) {
4592
+ const [, name, whenTrue = '', whenFalse = ''] = match;
4593
+ if (branchIsUserCopy(whenTrue) || branchIsUserCopy(whenFalse)) {
4594
+ fail(
4595
+ \`\${relative(filePath)} contains manual \${name} copy branching. Put user-facing copy in i18n JSON resources.\`,
4596
+ );
4597
+ }
4598
+ }
4599
+ }
4600
+
4601
+ function checkLiteralVisibleAttributes(filePath, text) {
4602
+ if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx')) {
4603
+ return;
4604
+ }
4605
+ for (const attribute of visibleCopyAttributes) {
4606
+ const pattern = new RegExp(\`\\\\b\${attribute}=["'][^"'{}]*[A-Za-z][^"'{}]*["']\`, 'u');
4607
+ if (pattern.test(text)) {
4608
+ fail(
4609
+ \`\${relative(filePath)} contains literal \${attribute} copy. Use t(...) or route metadata for visible text.\`,
4610
+ );
4611
+ }
4612
+ }
4613
+ }
4614
+
4615
+ function checkSplitPhraseKeys(filePath, text) {
4616
+ if (/t\\(\\s*['"][^'"]+\\.(?:prefix|suffix|before|after)['"]\\s*\\)/u.test(text)) {
4617
+ fail(
4618
+ \`\${relative(filePath)} uses split phrase translation keys. Keep translator-owned phrases whole.\`,
4619
+ );
4620
+ }
4621
+ }
4622
+
4623
+ function checkBoundaryAttributes(filePath, text) {
4624
+ if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx')) {
4625
+ return;
4626
+ }
4627
+ if (/\\bdata-mf-(?:remote|expose)=/u.test(text)) {
4628
+ fail(
4629
+ \`\${relative(filePath)} uses legacy data-mf-* boundary attributes. Use data-modern-boundary-id and data-modern-mf-expose.\`,
4630
+ );
4631
+ }
4632
+ }
4633
+
4634
+ function visitLocaleKeys(value, visitor, pathParts = []) {
4635
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
4636
+ return;
4637
+ }
4638
+ for (const [key, child] of Object.entries(value)) {
4639
+ const nextPath = [...pathParts, key];
4640
+ visitor(key, child, nextPath);
4641
+ visitLocaleKeys(child, visitor, nextPath);
4642
+ }
4643
+ }
4644
+
4645
+ function checkPluralResources(filePath, json) {
4646
+ const language = relative(filePath).split('/locales/')[1]?.split('/')[0];
4647
+ const requiredSuffixes =
4648
+ language === 'cs' ? ['one', 'few', 'many', 'other'] : ['one', 'other'];
4649
+ const groups = new Map();
4650
+
4651
+ visitLocaleKeys(json, (key, value, pathParts) => {
4652
+ if (typeof value === 'string' && value.includes('{{count}}')) {
4653
+ const suffixMatch = key.match(/^(.*)_(one|few|many|other)$/u);
4654
+ if (!suffixMatch) {
4655
+ fail(
4656
+ \`\${relative(filePath)} key \${pathParts.join('.')} contains {{count}} but is not plural-suffixed.\`,
4657
+ );
4658
+ }
4659
+ const [, base = '', suffix = ''] = suffixMatch;
4660
+ const parentPath = pathParts.slice(0, -1).join('.');
4661
+ const groupKey = \`\${parentPath}.\${base}\`;
4662
+ const existing = groups.get(groupKey) ?? new Set();
4663
+ existing.add(suffix);
4664
+ groups.set(groupKey, existing);
4665
+ }
4666
+ });
4667
+
4668
+ for (const [group, suffixes] of groups) {
4669
+ for (const suffix of requiredSuffixes) {
4670
+ if (!suffixes.has(suffix)) {
4671
+ fail(\`\${relative(filePath)} plural group \${group} is missing _\${suffix}.\`);
4672
+ }
4673
+ }
4674
+ }
4675
+ }
4676
+
4677
+ const sourceFiles = sourceRoots.flatMap(sourceRoot =>
4678
+ walk(path.join(root, sourceRoot)).filter(filePath => isSourceFile(filePath)),
4679
+ );
4680
+ for (const filePath of sourceFiles) {
4681
+ const text = readText(filePath);
4682
+ checkRuntimeResources(filePath, text);
4683
+ checkLanguageConditionals(filePath, text);
4684
+ checkLiteralVisibleAttributes(filePath, text);
4685
+ checkSplitPhraseKeys(filePath, text);
4686
+ checkBoundaryAttributes(filePath, text);
4687
+ }
4688
+
4689
+ const localeFiles = sourceRoots.flatMap(sourceRoot =>
4690
+ walk(path.join(root, sourceRoot)).filter(filePath => isLocaleJson(filePath)),
4691
+ );
4692
+ for (const filePath of localeFiles) {
4693
+ checkPluralResources(filePath, JSON.parse(readText(filePath)));
4694
+ }
4695
+
4696
+ console.log('UltraModern i18n and boundary guardrails validated');
4697
+ `;
4698
+ }
4495
4699
  function createWorkspaceValidationScript(scope, enableTailwind, remotes = []) {
4496
4700
  const verticals = remotes.filter(appHasEffectApi).map((remote)=>({
4497
4701
  id: remote.id,
@@ -4579,6 +4783,7 @@ const requiredPaths = [
4579
4783
  '.modernjs/ultramodern-generated-contract.json',
4580
4784
  'scripts/assert-mf-types.mjs',
4581
4785
  'scripts/bootstrap-agent-skills.mjs',
4786
+ 'scripts/check-ultramodern-i18n-boundaries.mjs',
4582
4787
  'scripts/proof-cloudflare-version.mjs',
4583
4788
  'scripts/setup-agent-reference-repos.mjs',
4584
4789
  'apps/shell-super-app/package.json',
@@ -4662,12 +4867,13 @@ assert(
4662
4867
  );
4663
4868
  assert(rootPackage.scripts?.['cloudflare:build'] === expectedCloudflareBuildScript, 'Root cloudflare:build script is incorrect');
4664
4869
  assert(rootPackage.scripts?.['ultramodern:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose ultramodern:check');
4870
+ assert(rootPackage.scripts?.['ultramodern:i18n-boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mjs', 'Root must expose ultramodern:i18n-boundaries');
4665
4871
  assert(rootPackage.scripts?.['ultramodern:assert-mf-types'] === 'node ./scripts/assert-mf-types.mjs', 'Root must expose ultramodern:assert-mf-types');
4666
4872
  assert(rootPackage.scripts?.['cloudflare:deploy'] === expectedCloudflareDeployScript, 'Root must expose cloudflare:deploy');
4667
4873
  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');
4668
4874
  assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
4669
4875
  assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
4670
- 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');
4876
+ assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)", 'Root postinstall must format, bootstrap agent skills, install reference repositories, and enable hooks last');
4671
4877
 
4672
4878
  const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
4673
4879
  assert(
@@ -4721,7 +4927,7 @@ assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, no
4721
4927
  for (const vertical of fullStackVerticals) {
4722
4928
  const packageJson = readJson(\`\${vertical.path}/package.json\`);
4723
4929
  assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
4724
- assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy', \`\${vertical.id} must expose cloudflare:deploy\`);
4930
+ 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\`);
4725
4931
  assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
4726
4932
  assert(packageJson.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
4727
4933
  assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
@@ -5124,6 +5330,7 @@ main().then(
5124
5330
  function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind, remotes = []) {
5125
5331
  writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript(remotes));
5126
5332
  writeFileReplacing(targetDir, "scripts/validate-ultramodern-workspace.mjs", createWorkspaceValidationScript(scope, enableTailwind, remotes));
5333
+ writeFileReplacing(targetDir, "scripts/check-ultramodern-i18n-boundaries.mjs", createWorkspaceI18nBoundaryValidationScript());
5127
5334
  writeFileReplacing(targetDir, "scripts/proof-cloudflare-version.mjs", createCloudflareVersionProofScript());
5128
5335
  }
5129
5336
  function writeApp(targetDir, scope, app, packageSource, enableTailwind, remotes = []) {
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.62",
24
+ "version": "3.2.0-ultramodern.64",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^25.9.1",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260527.2",
43
43
  "tsx": "^4.22.3",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.62"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.64"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org/",
@@ -54,6 +54,6 @@
54
54
  "start": "node ./dist/index.js"
55
55
  },
56
56
  "ultramodern": {
57
- "frameworkVersion": "3.2.0-ultramodern.62"
57
+ "frameworkVersion": "3.2.0-ultramodern.64"
58
58
  }
59
59
  }
@@ -10,7 +10,7 @@ This project is generated for Codex-first UltraModern.js work.
10
10
  - `pnpm i18n:check` rejects hardcoded user-visible JSX text.
11
11
  - `pnpm ultramodern:check` verifies the generated contract.
12
12
  - Generated Codex stop hooks and subagent-stop hooks run `pnpm format && pnpm lint:fix && pnpm ultramodern:check`.
13
- - `postinstall` installs `lefthook` when the app is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format`, `pnpm lint:fix`, and `pnpm ultramodern:check` on pre-commit; pre-push runs `pnpm ultramodern:check`.
13
+ - `postinstall` installs `lefthook` when the app is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm ultramodern:check` on pre-commit; pre-push runs `pnpm ultramodern:check`.
14
14
 
15
15
  ## Internationalization
16
16
 
@@ -1,13 +1,8 @@
1
1
  pre-commit:
2
2
  commands:
3
- format:
4
- run: pnpm format
3
+ fix-and-check:
4
+ run: pnpm format && pnpm lint:fix && pnpm ultramodern:check
5
5
  stage_fixed: true
6
- lint:
7
- run: pnpm lint:fix
8
- stage_fixed: true
9
- check:
10
- run: pnpm ultramodern:check
11
6
 
12
7
  pre-push:
13
8
  commands:
@@ -11,7 +11,7 @@ instructions, not optional reading.
11
11
  - `pnpm typecheck` runs effect-tsgo as the TypeScript checker.
12
12
  - `pnpm check` runs formatting, linting, effect-tsgo, private-skill availability checks, and the generated workspace contract.
13
13
  - Generated Codex stop hooks and subagent-stop hooks run `pnpm format && pnpm lint:fix && pnpm check`.
14
- - `postinstall` installs `lefthook` when the workspace is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format`, `pnpm lint:fix`, and `pnpm check` on pre-commit; pre-push runs `pnpm check`.
14
+ - `postinstall` formats the generated tree, installs agent skills and reference repos, then installs `lefthook` when the workspace is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm check` on pre-commit; pre-push runs `pnpm check`.
15
15
 
16
16
  ## Localized Routes
17
17
 
@@ -1,13 +1,8 @@
1
1
  pre-commit:
2
2
  commands:
3
- format:
4
- run: pnpm format
3
+ fix-and-check:
4
+ run: pnpm format && pnpm lint:fix && pnpm check
5
5
  stage_fixed: true
6
- lint:
7
- run: pnpm lint:fix
8
- stage_fixed: true
9
- check:
10
- run: pnpm check
11
6
 
12
7
  pre-push:
13
8
  commands:
@@ -8,6 +8,7 @@ export default defineConfig({
8
8
  '**/*.json',
9
9
  'dist',
10
10
  'node_modules',
11
+ 'repos/**',
11
12
  '.modern',
12
13
  '.modernjs',
13
14
  '**/routeTree.gen.ts',
@@ -12,6 +12,7 @@ export default defineConfig({
12
12
  '.agents',
13
13
  'dist',
14
14
  'node_modules',
15
+ 'repos/**',
15
16
  '.modern',
16
17
  '.modernjs',
17
18
  '**/routeTree.gen.ts',