@bleedingdev/modern-js-create 3.2.0-ultramodern.61 → 3.2.0-ultramodern.63

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