catalyst-core 1.0.2-canary.1 → 1.0.2
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/bin/catalyst.js +105 -1
- package/changelog.md +34 -0
- package/dist/cli/README.md +119 -0
- package/dist/cli/appConfig.js +51 -0
- package/dist/cli/buildErrors.js +170 -0
- package/dist/cli/buildWarnings.js +28 -0
- package/dist/cli/devOutput.js +524 -0
- package/dist/cli/diagnostic.js +388 -0
- package/dist/cli/hints.js +69 -0
- package/dist/cli/package.json +3 -0
- package/dist/cli/spinner.js +141 -0
- package/dist/cli/theme.js +155 -0
- package/dist/errors/index.js +7 -3
- package/dist/logger.js +61 -6
- package/dist/native/androidProject/app/build.gradle.kts +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +2 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +9 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashActivity.kt +65 -34
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashOverlay.kt +109 -0
- package/dist/native/androidSetup.js +46 -57
- package/dist/native/buildAndroid/assets.js +22 -13
- package/dist/native/buildAndroid/build.js +98 -26
- package/dist/native/buildAndroid/config.js +43 -12
- package/dist/native/buildAndroid/index.js +49 -191
- package/dist/native/buildAppAndroid.js +19 -8
- package/dist/native/buildAppIos.js +35 -16
- package/dist/native/buildIos/assets.js +1 -6
- package/dist/native/buildIos/build.js +66 -95
- package/dist/native/buildIos/config.js +26 -8
- package/dist/native/buildIos/index.js +143 -64
- package/dist/native/buildIos/plugins.js +12 -15
- package/dist/native/internalPluginUtils.js +0 -1
- package/dist/native/iosnativeWebView/CoreLogic/Sources/CatalystCoreLogic/Constants/ConfigConstants.swift +2 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashHTMLView.swift +31 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +17 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +41 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +2 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +3 -0
- package/dist/native/pluginComposerAndroid.js +2 -3
- package/dist/native/renameAndroidProject.js +42 -20
- package/dist/native/setupEmulatorIos.js +28 -49
- package/dist/native/setupServer.js +12 -15
- package/dist/native/terminalProgress.js +186 -147
- package/dist/native/utils.js +340 -54
- package/dist/otel.js +15 -9
- package/dist/scripts/build.js +313 -42
- package/dist/scripts/generateOfflineManifest.js +11 -1
- package/dist/scripts/loadEnvironmentVariables.js +24 -2
- package/dist/scripts/preServerInit.js +32 -10
- package/dist/scripts/preflight.js +35 -11
- package/dist/scripts/scriptUtils.js +34 -0
- package/dist/scripts/serve.js +64 -15
- package/dist/scripts/start.js +58 -21
- package/dist/server/expressServer.js +167 -32
- package/dist/server/renderer/handler.jsx +87 -11
- package/dist/server/utils/validator.js +14 -11
- package/dist/vite/manifest-categorization-plugin.js +7 -5
- package/dist/vite/resolveDevServerConfig.js +9 -2
- package/dist/vite/vite.config.client.js +18 -0
- package/dist/vite/vite.config.js +73 -13
- package/dist/vite/vite.config.server.js +21 -0
- package/dist/web-router/components/MetaTag.jsx +15 -2
- package/dist/web-router/components/MetaTag.test.jsx +71 -2
- package/dist/webmcp/WebMcpProvider.jsx +3 -2
- package/dist/webmcp/useTool.js +4 -1
- package/mcp_v2/conversion-tasks.json +6 -5
- package/mcp_v2/knowledge-base.json +124 -7
- package/mcp_v2/test/config.test.ts +59 -0
- package/mcp_v2/tools/build.js +6 -6
- package/mcp_v2/tools/config.js +35 -18
- package/mcp_v2/tools/conversion.js +7 -13
- package/package.json +5 -5
- package/test/cli/buildErrors.test.ts +173 -0
- package/test/cli/buildWarnings.test.cjs +62 -0
- package/test/cli/devOutput.test.ts +549 -0
- package/test/cli/diagnostic.test.ts +434 -0
- package/test/cli/output.test.cjs +90 -0
- package/test/cli/spinner.test.ts +177 -0
- package/test/cli/theme.test.ts +163 -0
- package/test/native/redactSecrets.test.ts +72 -0
- package/test/native/utils.test.ts +532 -0
- package/test/scripts/build.test.ts +275 -0
- package/test/scripts/serve.test.ts +102 -0
- package/test/server/fixtures/template/src/js/routes/index.jsx +8 -0
- package/test/server/handler.test.ts +17 -0
- package/test/server/logger.test.ts +185 -0
- package/test/server/otel.test.ts +556 -0
- package/test/server/validator.test.ts +19 -8
- package/dist/scripts/checkVersion.js +0 -3
- package/dist/scripts/cleanCache.js +0 -30
|
@@ -206,12 +206,13 @@
|
|
|
206
206
|
"detect_pattern": "field_check + file_exists",
|
|
207
207
|
"how_to_check": [
|
|
208
208
|
"file: config/config.json",
|
|
209
|
-
"field: splashScreen must exist at top level
|
|
210
|
-
"file: public/android/splashscreen.png
|
|
211
|
-
"file: public/ios/splashscreen.png
|
|
209
|
+
"field: WEBVIEW_CONFIG.splashScreen must exist (NOT at the top level of config.json)",
|
|
210
|
+
"file (optional): public/android/splashscreen.{png|jpg|jpeg|gif|bmp|webp}",
|
|
211
|
+
"file (optional): public/ios/splashscreen.{png|jpg|jpeg}",
|
|
212
|
+
"file (optional, 1.0.2+): public/splashscreen.html"
|
|
212
213
|
],
|
|
213
|
-
"fix_guide": "1) Add to
|
|
214
|
-
"verify_hint": "Build and install the app on a device/emulator. On cold launch (after force-closing), you should see the splash image for ~1-2 seconds before the app loads. If you see
|
|
214
|
+
"fix_guide": "1) Add WEBVIEW_CONFIG.splashScreen to config/config.json, e.g. { backgroundColor: '#ffffff', duration: 1000, imageWidth: 120, imageHeight: 120 }. There is no path field: images are found by filename. 2) Place the images at public/android/splashscreen.<ext> and public/ios/splashscreen.<ext>; without one, Android shows the launcher icon and iOS a loader. 3) Optional on catalyst-core 1.0.2+: add a self-contained public/splashscreen.html (inline CSS/JS, data: URI images, no network) for an animated splash shown after the native image until the first page loads; set splashScreen.minDuration (ms) to hold it longer. duration is ignored in that mode.",
|
|
215
|
+
"verify_hint": "Build and install the app on a device/emulator. On cold launch (after force-closing), you should see the splash image for ~1-2 seconds before the app loads. If you see no splash, check that splashScreen sits inside WEBVIEW_CONFIG (not at the top level of config.json) and that the image filename is splashscreen.<ext>.",
|
|
215
216
|
"depends_on": ["T1_CONFIG"]
|
|
216
217
|
},
|
|
217
218
|
{
|
|
@@ -490,7 +490,7 @@
|
|
|
490
490
|
{
|
|
491
491
|
"section": "config_structure",
|
|
492
492
|
"title": "splashScreen Config Structure",
|
|
493
|
-
"content": "splashScreen sits INSIDE WEBVIEW_CONFIG (not at top
|
|
493
|
+
"content": "splashScreen sits INSIDE WEBVIEW_CONFIG (not at the top level of config.json); the Android and iOS builds read only WEBVIEW_CONFIG.splashScreen. All fields are optional. Omitting splashScreen turns the Catalyst splash off and the app goes straight to the WebView. Fields: backgroundColor (string hex, default '#ffffff'), duration (number ms; Android defaults to 1000; on iOS, when unset, the splash is dismissed once the WebView finishes loading; ignored when public/splashscreen.html exists), minDuration (number ms, default 0, catalyst-core 1.0.2+; the minimum time the HTML splash stays up; a negative or non-number value is ignored with a build warning), imageWidth (number px, default 120), imageHeight (number px, default 120), cornerRadius (number px, default 20; 0 for square corners). Example: WEBVIEW_CONFIG.splashScreen = { backgroundColor: '#ffffff', duration: 2000, imageWidth: 400, imageHeight: 200, cornerRadius: 20 }. Asset placement: put image at public/android/splashscreen.{png|jpg|jpeg|gif|bmp|webp} for Android, public/ios/splashscreen.{png|jpg|jpeg} for iOS. Asset is auto-discovered by filename \u2014 NO path field in config. Missing image = build proceeds; Android shows the launcher icon, iOS a loader. backgroundColor is applied to Android themes.xml (windowSplashScreenBackground). Optional animated splash (1.0.2+): public/splashscreen.html, see 'HTML Splash Screen (1.0.2)'. Pfizer example: splashScreen: { imageHeight: 200, imageWidth: 400 } (backgroundColor omits = defaults to white).",
|
|
494
494
|
"layer": "Config",
|
|
495
495
|
"source": "static",
|
|
496
496
|
"tags": ["splashscreen", "splash", "config", "webview_config", "android", "ios", "native", "assets"]
|
|
@@ -958,7 +958,7 @@
|
|
|
958
958
|
{
|
|
959
959
|
"section": "observability",
|
|
960
960
|
"title": "OpenTelemetry Setup in Catalyst",
|
|
961
|
-
"content": "Catalyst provides built-in OpenTelemetry support via catalyst-core/otel. Server-side only.\n\nUSAGE: Call Otel.init() inside preServerInit() in server/index.js. Guard with an ENABLE flag and skip in development.\n\nimport Otel from 'catalyst-core/otel'\n\nexport const preServerInit = () => {\n const otelConfig = JSON.parse(process.env.OTEL)\n if (otelConfig.ENABLE && process.env.NODE_ENV !== 'development') {\n Otel.init({\n serviceName: 'my-catalyst-app',\n serviceVersion: process.env.RELEASE_TAG,\n environment: process.env.ENV_NAME,\n traceUrl: otelConfig.ENDPOINT,\n instrumentations: [ new HttpInstrumentation({ ... }) ],\n })\n }\n}\n\nCONFIG SHAPE (all optional, defaults shown):\n- serviceName: 'catalyst-server'\n- serviceVersion: '1.0.0'\n- environment: 'development'\n- traceUrl: 'http://localhost:4318/v1/traces'\n- metricUrl: 'http://localhost:4318/v1/metrics'\n- traceHeaders: {}\n- metricHeaders: {}\n- exportIntervalMillis: 10000\n- exportTimeoutMillis: 10000\n- instrumentations: [getNodeAutoInstrumentations()] by default\n\nAUTO METRICS: init() automatically collects process_cpu_usage_percent, process_memory_usage_bytes, process_memory_heap_used_bytes, process_memory_heap_total_bytes \u2014 no extra config needed.\n\nGRACEFUL SHUTDOWN: SIGTERM and SIGINT handlers registered automatically \u2014 sdk.shutdown() is called on process exit.\n\nRETURN VALUE: { sdk, meter } \u2014 use meter (OTel Meter instance) to create your own custom metrics via meter.createObservableGauge(), createCounter(), etc.\n\nPEER DEPS: Install @opentelemetry/* packages yourself \u2014 not bundled by catalyst-core.",
|
|
961
|
+
"content": "Catalyst provides built-in OpenTelemetry support via catalyst-core/otel. Server-side only.\n\nUSAGE: Call Otel.init() inside preServerInit() in server/index.js. Guard with an ENABLE flag and skip in development.\n\nimport Otel from 'catalyst-core/otel'\n\nexport const preServerInit = () => {\n const otelConfig = JSON.parse(process.env.OTEL)\n if (otelConfig.ENABLE && process.env.NODE_ENV !== 'development') {\n Otel.init({\n serviceName: 'my-catalyst-app',\n serviceVersion: process.env.RELEASE_TAG,\n environment: process.env.ENV_NAME,\n traceUrl: otelConfig.ENDPOINT,\n instrumentations: [ new HttpInstrumentation({ ... }) ],\n })\n }\n}\n\nCONFIG SHAPE (all optional, defaults shown):\n- serviceName: 'catalyst-server'\n- serviceVersion: '1.0.0'\n- environment: 'development'\n- traceUrl: 'http://localhost:4318/v1/traces'\n- metricUrl: 'http://localhost:4318/v1/metrics'\n- traceHeaders: {}\n- metricHeaders: {}\n- exportIntervalMillis: 10000\n- exportTimeoutMillis: 10000\n- instrumentations: [getNodeAutoInstrumentations()] by default\n\nAUTO METRICS: init() automatically collects process_cpu_usage_percent, process_memory_usage_bytes, process_memory_heap_used_bytes, process_memory_heap_total_bytes \u2014 no extra config needed.\n\nGRACEFUL SHUTDOWN: SIGTERM and SIGINT handlers registered automatically \u2014 sdk.shutdown() is called on process exit.\n\nRETURN VALUE: { sdk, meter } \u2014 use meter (OTel Meter instance) to create your own custom metrics via meter.createObservableGauge(), createCounter(), etc.\n\nPEER DEPS: Install @opentelemetry/* packages yourself \u2014 not bundled by catalyst-core.\n\nLOGGING: init() logs through the app-wide logger that configureLogger() from catalyst-core/logger installs. Before 1.0.2, calling Otel.init() in an app that never called configureLogger() threw ReferenceError: logger is not defined. From 1.0.2 it falls back to console until a global logger is set, and picks up a logger configured later. On older versions, call configureLogger() before Otel.init().",
|
|
962
962
|
"layer": "Observability",
|
|
963
963
|
"source": "static",
|
|
964
964
|
"tags": [
|
|
@@ -1257,7 +1257,7 @@
|
|
|
1257
1257
|
{
|
|
1258
1258
|
"section": "framework_identity",
|
|
1259
1259
|
"title": "Catalyst Runtime Generations",
|
|
1260
|
-
"content": "Always determine the installed catalyst-core version before giving web runtime guidance. Catalyst 0.2.x is the legacy webpack generation and uses @tata1mg/router plus @loadable/component. Catalyst 0.3.x, beginning with 0.3.0-beta.1, is the Vite/ESM generation: router APIs are exported by catalyst-core and split()/hydrationReady() replace loadable APIs. Catalyst 1.0.0 is the API freeze (there is no 0.4.0 release); see 'Upgrading catalyst-core 0.3.x to 1.0.0'. Do not combine instructions from different generations. If no version can be detected, ask which version the project targets.",
|
|
1260
|
+
"content": "Always determine the installed catalyst-core version before giving web runtime guidance. Catalyst 0.2.x is the legacy webpack generation and uses @tata1mg/router plus @loadable/component. Catalyst 0.3.x, beginning with 0.3.0-beta.1, is the Vite/ESM generation: router APIs are exported by catalyst-core and split()/hydrationReady() replace loadable APIs. Catalyst 1.0.0 is the API freeze (there is no 0.4.0 release); see 'Upgrading catalyst-core 0.3.x to 1.0.0'. Do not combine instructions from different generations. If no version can be detected, ask which version the project targets. Patch releases after 1.0.0 (1.0.1, 1.0.2) keep the frozen API; see 'Upgrading catalyst-core 1.0.x to 1.0.2'.",
|
|
1261
1261
|
"layer": "Framework",
|
|
1262
1262
|
"source": "static",
|
|
1263
1263
|
"tags": ["versions", "0.2.x", "0.3.x", "1.0.0", "migration", "webpack", "vite"],
|
|
@@ -1281,10 +1281,22 @@
|
|
|
1281
1281
|
{
|
|
1282
1282
|
"section": "build_system",
|
|
1283
1283
|
"title": "Catalyst 0.3.x CLI Commands",
|
|
1284
|
-
"content": "Current web commands are catalyst start, catalyst build, catalyst serve, and catalyst serve:inspect. devBuild and devServe are legacy 0.2.x commands and are not accepted by the 0.3.x CLI. Native commands remain buildApp, buildApp:ios, buildApp:android, setupEmulator, setupEmulator:ios, and setupEmulator:android.",
|
|
1284
|
+
"content": "Current web commands are catalyst start, catalyst build, catalyst serve, and catalyst serve:inspect. devBuild and devServe are legacy 0.2.x commands and are not accepted by the 0.3.x CLI. Native commands remain buildApp, buildApp:ios, buildApp:android, setupEmulator, setupEmulator:ios, and setupEmulator:android. catalyst-core 1.0.2 OUTPUT: every command prints a short header, one line per step with timing, and one diagnostic block on failure. Flags: --verbose shows every step and, on failure, the full tool output (bundler, Gradle); --debug also prints stacks and the environment. Child processes inherit the mode through CATALYST_OUTPUT_MODE=verbose|debug. Failures carry registry codes from the error catalog (for example BUNDLE-000, ANDROID-000, IOS-000, CCA-001); use the explain_error tool for any code. catalyst serve (1.0.2+) exits when there is no build in BUILD_OUTPUT_PATH and refuses a stale build with PREFLIGHT-022 when PUBLIC_STATIC_ASSET_URL, PUBLIC_STATIC_ASSET_PATH or a CLIENT_ENV_VARIABLES value in config/config.json changed after catalyst build (the build records them in <BUILD_OUTPUT_PATH>/.catalyst-build.json; a build from an older version has no record, so serve warns and skips the check until you rebuild). Fix: run catalyst build again, then catalyst serve. catalyst serve passes the app's own stdout/stderr through unchanged, so JSON log shippers keep working. Scripts that parsed the old CLI text output must be updated.",
|
|
1285
1285
|
"layer": "Build",
|
|
1286
1286
|
"source": "static",
|
|
1287
|
-
"tags": [
|
|
1287
|
+
"tags": [
|
|
1288
|
+
"0.3.x",
|
|
1289
|
+
"1.0.2",
|
|
1290
|
+
"cli",
|
|
1291
|
+
"start",
|
|
1292
|
+
"build",
|
|
1293
|
+
"serve",
|
|
1294
|
+
"native",
|
|
1295
|
+
"verbose",
|
|
1296
|
+
"debug",
|
|
1297
|
+
"stale-build",
|
|
1298
|
+
"PREFLIGHT-022"
|
|
1299
|
+
],
|
|
1288
1300
|
"github_files": ["packages/catalyst-core/bin/catalyst.js"]
|
|
1289
1301
|
},
|
|
1290
1302
|
{
|
|
@@ -1303,7 +1315,7 @@
|
|
|
1303
1315
|
{
|
|
1304
1316
|
"section": "routing",
|
|
1305
1317
|
"title": "Catalyst 0.3.x Integrated Router",
|
|
1306
|
-
"content": "Catalyst 0.3.x removes the @tata1mg/router package dependency. As of 1.0.0 (the API freeze), catalyst-core does NOT re-export React Router. Its root entry
|
|
1318
|
+
"content": "Catalyst 0.3.x removes the @tata1mg/router package dependency. As of 1.0.0 (the API freeze), catalyst-core does NOT re-export React Router. Its root entry exported exactly eight names in 1.0.0: RouterDataProvider, useCurrentRouteData, useRouterData, MetaTag, split, hydrationReady, Head, Body. 1.0.2 adds a ninth, useRouterState (returns { location, params, navigate, matchedRoutes } through Catalyst's router context; used by catalyst-core/webmcp; throws outside RouterDataProvider). App components should still use react-router's useLocation/useParams/useNavigate. There is no default export (split was previously also the default). React Router is a peerDependency (^7.18.2): install react-router in the application and import Outlet, Link, useParams, useNavigate, useLocation, Navigate and every other router name from 'react-router' directly. Import RouterProvider in client/index.js from 'react-router/dom', which passes React DOM's flushSync to the router. Metadata utilities (getMetaData, mergeHeadElements, deleteHeadTagsByDataAttribute), RouterContext, serverDataFetcher and useNavigateWithTransition are no longer public. Existing route arrays and preparedRoutes structure remain unchanged. Keeping both router packages can create duplicate runtime contracts and invalid React element errors.",
|
|
1307
1319
|
"layer": "Runtime",
|
|
1308
1320
|
"source": "static",
|
|
1309
1321
|
"tags": [
|
|
@@ -1377,7 +1389,7 @@
|
|
|
1377
1389
|
{
|
|
1378
1390
|
"section": "version_migration",
|
|
1379
1391
|
"title": "Upgrading catalyst-core 0.3.x to 1.0.0",
|
|
1380
|
-
"content": "Upgrade path for an application on catalyst-core 0.3.x to 1.0.0, the stable release. Read the installed catalyst-core version from package.json first. 0.3.0-beta.5 upgrades directly. 0.3.0-beta.1 to 0.3.0-beta.4 used react-router-dom 6, so those also cross React Router 6 to 7 (step 1). Older applications first move to 0.3.x (see 'Legacy 0.2.x Migration Phase 0' and 'Catalyst 0.3.x React Runtime Contract'). 1.0.0 is the API freeze; there is no 0.4.0 release. Do the steps in order.\n\n1. DEPENDENCIES: set catalyst-core to 1.0.0. Add react-router ^7.18.2 as an application dependency: it is now a peer dependency and the server exits at startup if it is missing or outside that range. Keep react and react-dom pinned to 19.0.0. TypeScript applications also need @types/react ^19.0.0. catalyst-core no longer installs react-router-dom, so change any import from 'react-router-dom' to 'react-router', except RouterProvider, which comes from 'react-router/dom' (step 3).\n\n2. NODE: run node -v. catalyst-core 1.0.0 requires >=22.12; on Node 20, importing catalyst-core/hooks or catalyst-core/WebBridge fails with ERR_REQUIRE_ESM. If the runtime is older, stop and tell the user; do not change the project's Node pin yourself.\n\n3. IMPORTS: the root entry exports exactly RouterDataProvider, useCurrentRouteData, useRouterData, MetaTag, split, hydrationReady, Head, Body. Move every React Router name (Link, Outlet, useParams, useNavigate, useLocation, Navigate, ...) from 'catalyst-core' to 'react-router'. In client/index.js import RouterProvider from 'react-router/dom', not 'react-router': only the /dom export passes React DOM's flushSync to the router, and the wrong one renders without any error. Replace `import split from 'catalyst-core'` with `import { split } from 'catalyst-core'`; there is no default export. Replace imports from 'catalyst-core/document' with Head and Body from 'catalyst-core'. 'catalyst-core/caching' is removed with no replacement. RouterContext, serverDataFetcher, mergeHeadElements, deleteHeadTagsByDataAttribute, getMetaData, useNavigateWithTransition and sanitizeFilePickerOptions (catalyst-core/hooks) are internal with no replacement: if the application uses one, stop and tell the user instead of inventing a substitute.\n\n4. CONFIG: catalyst start, build and serve now run a preflight and exit before starting when config/config.json lacks NODE_SERVER_HOSTNAME, NODE_SERVER_PORT, BUILD_OUTPUT_PATH, PUBLIC_STATIC_ASSET_PATH, PUBLIC_STATIC_ASSET_URL or CLIENT_ENV_VARIABLES, or when package.json _moduleAliases lacks @api, @containers, @server, @config, @css or @routes. Apps created from the templates already have all of them. WEBPACK_DEV_SERVER_HOSTNAME, WEBPACK_DEV_SERVER_PORT and ANALYZE_BUNDLE are no longer read and can be removed.\n\n5. BUILD AND START: run the build, then start the server once. The build catches removed imports (they fail instead of resolving to undefined); startup catches the react-router range and the preflight. Nothing checks the Node version at startup; an old Node fails only when hooks or WebBridge is imported (step 2).\n\n6. HOOK ERROR FIELDS (not caught by the build): hook errors are now CatalystError instances. Rename reads of error.action to error.suggestedAction and error.nativeError to error.cause; error.recoverable and error.timestamp no longer exist. error.code values are registry codes (RUNTIME-NATIVE-xxx) instead of names like PERMISSION_DENIED, so replace string-literal comparisons with ERROR_CODES from catalyst-core/errors (for example ERROR_CODES.RUNTIME_NATIVE_PERMISSION_DENIED). error.category is now 'RUNTIME-NATIVE' instead of PERMISSION, NETWORK, FILE_SYSTEM and the other old values. JSON.stringify(error) no longer includes the message; log error.message. See 'CatalystError shape (1.0.0)'.\n\n7. OPTIONAL, NON-BREAKING: rename deprecated hook alias keys to the canonical ones (see 'Native hooks \u2014 aliases and deprecations'); aliases keep working until 2.0.\n\nUnchanged: route arrays, preparedRoutes, server/index.js, server/server.js and server/document.js contracts, server hooks, and WEBVIEW_CONFIG. Full guide: docs/content/12-Migration/02-Version-Upgrades.md.",
|
|
1392
|
+
"content": "Upgrade path for an application on catalyst-core 0.3.x to 1.0.0, the stable release. Read the installed catalyst-core version from package.json first. 0.3.0-beta.5 upgrades directly. 0.3.0-beta.1 to 0.3.0-beta.4 used react-router-dom 6, so those also cross React Router 6 to 7 (step 1). Older applications first move to 0.3.x (see 'Legacy 0.2.x Migration Phase 0' and 'Catalyst 0.3.x React Runtime Contract'). 1.0.0 is the API freeze; there is no 0.4.0 release. Do the steps in order.\n\n1. DEPENDENCIES: set catalyst-core to 1.0.0. Add react-router ^7.18.2 as an application dependency: it is now a peer dependency and the server exits at startup if it is missing or outside that range. Keep react and react-dom pinned to 19.0.0. TypeScript applications also need @types/react ^19.0.0. catalyst-core no longer installs react-router-dom, so change any import from 'react-router-dom' to 'react-router', except RouterProvider, which comes from 'react-router/dom' (step 3).\n\n2. NODE: run node -v. catalyst-core 1.0.0 requires >=22.12; on Node 20, importing catalyst-core/hooks or catalyst-core/WebBridge fails with ERR_REQUIRE_ESM. If the runtime is older, stop and tell the user; do not change the project's Node pin yourself.\n\n3. IMPORTS: in 1.0.0 the root entry exports exactly RouterDataProvider, useCurrentRouteData, useRouterData, MetaTag, split, hydrationReady, Head, Body (1.0.2 adds useRouterState). Move every React Router name (Link, Outlet, useParams, useNavigate, useLocation, Navigate, ...) from 'catalyst-core' to 'react-router'. In client/index.js import RouterProvider from 'react-router/dom', not 'react-router': only the /dom export passes React DOM's flushSync to the router, and the wrong one renders without any error. Replace `import split from 'catalyst-core'` with `import { split } from 'catalyst-core'`; there is no default export. Replace imports from 'catalyst-core/document' with Head and Body from 'catalyst-core'. 'catalyst-core/caching' is removed with no replacement. RouterContext, serverDataFetcher, mergeHeadElements, deleteHeadTagsByDataAttribute, getMetaData, useNavigateWithTransition and sanitizeFilePickerOptions (catalyst-core/hooks) are internal with no replacement: if the application uses one, stop and tell the user instead of inventing a substitute.\n\n4. CONFIG: catalyst start, build and serve now run a preflight and exit before starting when config/config.json lacks NODE_SERVER_HOSTNAME, NODE_SERVER_PORT, BUILD_OUTPUT_PATH, PUBLIC_STATIC_ASSET_PATH, PUBLIC_STATIC_ASSET_URL or CLIENT_ENV_VARIABLES, or when package.json _moduleAliases lacks @api, @containers, @server, @config, @css or @routes. Apps created from the templates already have all of them. WEBPACK_DEV_SERVER_HOSTNAME, WEBPACK_DEV_SERVER_PORT and ANALYZE_BUNDLE are no longer read and can be removed.\n\n5. BUILD AND START: run the build, then start the server once. The build catches removed imports (they fail instead of resolving to undefined); startup catches the react-router range and the preflight. Nothing checks the Node version at startup; an old Node fails only when hooks or WebBridge is imported (step 2).\n\n6. HOOK ERROR FIELDS (not caught by the build): hook errors are now CatalystError instances. Rename reads of error.action to error.suggestedAction and error.nativeError to error.cause; error.recoverable and error.timestamp no longer exist. error.code values are registry codes (RUNTIME-NATIVE-xxx) instead of names like PERMISSION_DENIED, so replace string-literal comparisons with ERROR_CODES from catalyst-core/errors (for example ERROR_CODES.RUNTIME_NATIVE_PERMISSION_DENIED). error.category is now 'RUNTIME-NATIVE' instead of PERMISSION, NETWORK, FILE_SYSTEM and the other old values. JSON.stringify(error) no longer includes the message; log error.message. See 'CatalystError shape (1.0.0)'.\n\n7. OPTIONAL, NON-BREAKING: rename deprecated hook alias keys to the canonical ones (see 'Native hooks \u2014 aliases and deprecations'); aliases keep working until 2.0.\n\nUnchanged: route arrays, preparedRoutes, server/index.js, server/server.js and server/document.js contracts, server hooks, and WEBVIEW_CONFIG. Full guide: docs/content/12-Migration/02-Version-Upgrades.md.",
|
|
1381
1393
|
"layer": "Framework",
|
|
1382
1394
|
"source": "static",
|
|
1383
1395
|
"tags": [
|
|
@@ -1457,5 +1469,110 @@
|
|
|
1457
1469
|
"layer": "Bridge",
|
|
1458
1470
|
"source": "static",
|
|
1459
1471
|
"tags": "ai,catalyst-ai,native ai,on-device,gemma,gemma 4,e2b,litert,litert-lm,foundation models,apple intelligence,useAI,useNativeAI"
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
"section": "config_structure",
|
|
1475
|
+
"title": "HTML Splash Screen (1.0.2)",
|
|
1476
|
+
"content": "Available from catalyst-core 1.0.2 on Android and iOS. Add public/splashscreen.html (the root of public/, not public/android or public/ios; one file shared by both platforms) to show an HTML/CSS animation while the app loads. It takes effect only when WEBVIEW_CONFIG.splashScreen is set; without that key the file is ignored.\n\nSEQUENCE: native splash image (public/<platform>/splashscreen.<ext>) \u2192 the HTML splash \u2192 the app. The HTML splash sits above the main WebView, which loads underneath, and is dismissed once the first page has finished loading and minDuration has passed. Hard cap: it is dismissed after 10 seconds even if the page is still loading.\n\nCONFIG: WEBVIEW_CONFIG.splashScreen.minDuration (ms, default 0) keeps the animation on screen at least that long. A negative or non-number value is ignored (0) with a build warning. duration is ignored in HTML mode. Example: { \"WEBVIEW_CONFIG\": { \"splashScreen\": { \"backgroundColor\": \"#ffffff\", \"minDuration\": 1200 } } }.\n\nFILE RULES: one self-contained file: inline CSS and JS, images as data: URIs. Paint your own opaque, full-screen background (the native image sits underneath). No network requests (Android blocks network loads in the splash WebView). No native bridge (no JS interfaces are exposed). Prefer CSS transform and opacity animations, since the app is loading at the same time.\n\nBUILD: iOS embeds the file in the app binary at build time and warns above 1 MB; Android copies it to assets/splash/. Rebuild the native app after editing it. Delete the file to go back to the image-only splash (the Android build also removes its copy). check_config reports the file and validates minDuration.",
|
|
1477
|
+
"layer": "Config",
|
|
1478
|
+
"source": "static",
|
|
1479
|
+
"tags": [
|
|
1480
|
+
"1.0.2",
|
|
1481
|
+
"splashscreen",
|
|
1482
|
+
"splash",
|
|
1483
|
+
"splashscreen.html",
|
|
1484
|
+
"html-splash",
|
|
1485
|
+
"animated-splash",
|
|
1486
|
+
"minDuration",
|
|
1487
|
+
"native",
|
|
1488
|
+
"android",
|
|
1489
|
+
"ios"
|
|
1490
|
+
],
|
|
1491
|
+
"github_files": [
|
|
1492
|
+
"docs/content/02-Guides and Tutorials/02-First Universal App/03-Configuration.mdx",
|
|
1493
|
+
"packages/catalyst-core/src/native/buildIos/config.js",
|
|
1494
|
+
"packages/catalyst-core/src/native/androidProject/app/src/main/java/io/yourname/androidproject/SplashOverlay.kt"
|
|
1495
|
+
]
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
"section": "webmcp",
|
|
1499
|
+
"title": "WebMCP (experimental, 1.0.2) \u2014 Setup",
|
|
1500
|
+
"content": "WebMCP lets a page expose tools (named actions with a JSON-schema input) to an AI agent running in the browser. Catalyst ships it from catalyst-core 1.0.2 as catalyst-core/webmcp. EXPERIMENTAL: useTool, WebMcpProvider, the route `tool` field, the webmcp:* document events and inspect/invokeTool may change in a minor release. NOT the same as this MCP server: this server helps AI coding tools understand a Catalyst project (Node, build time); WebMCP runs in the app's browser tab and exposes the app's own actions to agents.\n\nSETUP: call installShim() from catalyst-core/webmcp/shim once, before the provider mounts. Catalyst targets this opt-in DOM shim; native browser document.modelContext is intentionally unsupported for now. Then wrap the app in WebMcpProvider inside RouterDataProvider, passing the app's own route table:\n\nimport { WebMcpProvider } from 'catalyst-core/webmcp'\nimport { installShim } from 'catalyst-core/webmcp/shim'\nimport routes from './routes'\ninstallShim()\nexport const App = ({ children }) => (\n <WebMcpProvider routes={routes} filterNavigable={(route) => !route.path.startsWith('/docs/')}>{children}</WebMcpProvider>\n)\n\nfilterNavigable is optional and keeps large page sets out of the navigate tool's enum.\n\nBUILT-IN TOOLS (registered by the provider): navigate, get_current_route, get_page_info (built from the page's setMetaData). Agent side (shim): document.modelContext.getTools() and document.modelContext.executeTool(name, args).\n\nEXPORTS of catalyst-core/webmcp: WebMcpProvider, useTool, WebMcpError, WEBMCP_ERROR_CODES, inspect, invokeTool, isNative, plus helpers (flattenRoutes, declarativeToolFor, frameworkTools, toolNameFromPath, paramNames, fillPath, buildInputSchema, pageInfoTool, flattenHeadElements). catalyst-core/webmcp/shim exports installShim. A dev panel is app-level: listen for webmcp:register, webmcp:unregister, webmcp:execute:start and webmcp:execute:end on document and call inspect()/invokeTool(). Full guide: docs/content/20-WebMCP.md; example app: examples/webmcp-poc.",
|
|
1501
|
+
"layer": "Runtime",
|
|
1502
|
+
"source": "static",
|
|
1503
|
+
"tags": [
|
|
1504
|
+
"1.0.2",
|
|
1505
|
+
"webmcp",
|
|
1506
|
+
"web-mcp",
|
|
1507
|
+
"agents",
|
|
1508
|
+
"ai-agent",
|
|
1509
|
+
"modelContext",
|
|
1510
|
+
"WebMcpProvider",
|
|
1511
|
+
"installShim",
|
|
1512
|
+
"shim",
|
|
1513
|
+
"experimental",
|
|
1514
|
+
"tools"
|
|
1515
|
+
],
|
|
1516
|
+
"github_files": [
|
|
1517
|
+
"docs/content/20-WebMCP.md",
|
|
1518
|
+
"packages/catalyst-core/src/webmcp/index.js",
|
|
1519
|
+
"packages/catalyst-core/src/webmcp/shim.js"
|
|
1520
|
+
]
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
"section": "webmcp",
|
|
1524
|
+
"title": "WebMCP \u2014 Authoring Tools (route tool, useTool, errors)",
|
|
1525
|
+
"content": "Three ways to author WebMCP tools (catalyst-core 1.0.2+, experimental):\n\n1. DECLARATIVE, on the route: add `tool` to a route and Catalyst derives the name from the path (static segments joined by '_', '/' is 'home'), the input schema (path params required, `searchParams` optional) and an execute that navigates. Route tools default to annotations { readOnlyHint: true } and are available app-wide.\n{ path: '/products', component: ProductList, tool: { description: 'Browse the catalogue, optionally filtered.', searchParams: { category: { type: 'string', enum: ['footwear', 'apparel'] } } } }\n\n2. IMPERATIVE, in a component: useTool(spec) registers an action tool for the component's lifetime and removes it on unmount or when the user leaves the route. spec = { name, description, inputSchema, annotations?, execute }. Returns { status, receipt, error }; the receipt is a registration handle, not an execution result.\nuseTool({ name: 'add_to_cart', description: 'Add the current product to the cart.', inputSchema: { type: 'object', properties: { quantity: { type: 'number', minimum: 1 } } }, execute: async ({ quantity = 1 }) => { dispatch(addItem({ id, quantity })); return { added: true } } })\n\n3. STATEFUL, with deps: useTool(spec, deps) refreshes description, inputSchema, annotations and execute in place when a dep changes (for example an enum of cart item ids), so a call already in flight still completes.\n\nERRORS: throw new WebMcpError with a code from WEBMCP_ERROR_CODES (TOOL_NOT_FOUND, INVALID_ARGS, EXECUTE_FAILED, ABORTED, DUPLICATE_TOOL; values are prefixed WEBMCP_, e.g. 'WEBMCP_INVALID_ARGS'). Arguments in the schema's `required` array are checked before execute runs.\n\nRULES: do side effects inside the tool (agents ignore 'now call X' hints in results). Mark intent with annotations: { readOnlyHint: true } for reads, { consequentialHint: true } for irreversible actions such as checkout. Keep names unique: route tool names must not collide with navigate, get_current_route or get_page_info; a collision logs a warning and the later tool is dropped.",
|
|
1526
|
+
"layer": "Runtime",
|
|
1527
|
+
"source": "static",
|
|
1528
|
+
"tags": [
|
|
1529
|
+
"1.0.2",
|
|
1530
|
+
"webmcp",
|
|
1531
|
+
"useTool",
|
|
1532
|
+
"route-tool",
|
|
1533
|
+
"declarative",
|
|
1534
|
+
"WebMcpError",
|
|
1535
|
+
"WEBMCP_ERROR_CODES",
|
|
1536
|
+
"readOnlyHint",
|
|
1537
|
+
"consequentialHint",
|
|
1538
|
+
"experimental",
|
|
1539
|
+
"tools"
|
|
1540
|
+
],
|
|
1541
|
+
"github_files": [
|
|
1542
|
+
"docs/content/20-WebMCP.md",
|
|
1543
|
+
"packages/catalyst-core/src/webmcp/useTool.js",
|
|
1544
|
+
"packages/catalyst-core/src/webmcp/declarative.js",
|
|
1545
|
+
"packages/catalyst-core/src/webmcp/errors.js"
|
|
1546
|
+
]
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
"section": "version_migration",
|
|
1550
|
+
"title": "Upgrading catalyst-core 1.0.x to 1.0.2",
|
|
1551
|
+
"content": "1.0.2 is a patch release on the frozen 1.0 API; most applications need no code change. Read the installed catalyst-core version from package.json first; apps on 0.3.x follow 'Upgrading catalyst-core 0.3.x to 1.0.0' first.\n\n1. DEPENDENCIES: set catalyst-core to 1.0.2. Node stays >=22.12; react and react-dom stay 19.0.0; react-router stays ^7.18.2.\n\n2. react-helmet-async REMOVED: catalyst-core no longer depends on it. MetaTag and setMetaData work unchanged; the tags are now rendered through React 19 document metadata, and the server-rendered copies are removed once the client copies are in, so stale head tags no longer survive navigation. If the application imports react-helmet-async itself (it used to arrive transitively), add it to the application's own dependencies.\n\n3. CLI OUTPUT: start, build, serve and the native builds print a short header, one line per step and one diagnostic block on failure; --verbose and --debug show more (see 'Catalyst 0.3.x CLI Commands'). Update any script that parsed the old text output. catalyst serve now refuses a stale build (PREFLIGHT-022): after changing PUBLIC_STATIC_ASSET_URL, PUBLIC_STATIC_ASSET_PATH or CLIENT_ENV_VARIABLES values, rebuild before serving. Run catalyst build once after upgrading so serve can run that check.\n\n4. FIXES, nothing to do: Fast Refresh works for JSX in .js files; Otel.init() no longer throws without configureLogger() (falls back to console); Head and Body no longer import prop-types at runtime, so apps that do not install prop-types no longer crash in SSR.\n\n5. NEW, OPT-IN: an animated HTML splash via public/splashscreen.html plus WEBVIEW_CONFIG.splashScreen.minDuration ('HTML Splash Screen (1.0.2)'); WebMCP via catalyst-core/webmcp, experimental ('WebMCP (experimental, 1.0.2) \u2014 Setup').\n\n6. NEW APPS ONLY: apps created by the create-catalyst-app released with catalyst-core 1.0.2 declare \"type\": \"module\" in package.json, and the Tailwind template ships postcss.config.cjs. catalyst-core reads postcss.config.cjs or postcss.config.js, so existing apps need no change. create-catalyst-app runs on Node >=20.12. NEW ROOT EXPORT: useRouterState is added to the root entry (now nine names); existing imports are unaffected.",
|
|
1552
|
+
"layer": "Framework",
|
|
1553
|
+
"source": "static",
|
|
1554
|
+
"tags": [
|
|
1555
|
+
"migration",
|
|
1556
|
+
"upgrade",
|
|
1557
|
+
"update",
|
|
1558
|
+
"bump",
|
|
1559
|
+
"1.0.2",
|
|
1560
|
+
"1.0.1",
|
|
1561
|
+
"1.0.x",
|
|
1562
|
+
"patch",
|
|
1563
|
+
"react-helmet-async",
|
|
1564
|
+
"helmet",
|
|
1565
|
+
"verbose",
|
|
1566
|
+
"stale-build",
|
|
1567
|
+
"PREFLIGHT-022",
|
|
1568
|
+
"splashscreen.html",
|
|
1569
|
+
"webmcp",
|
|
1570
|
+
"latest",
|
|
1571
|
+
"useRouterState"
|
|
1572
|
+
],
|
|
1573
|
+
"github_files": [
|
|
1574
|
+
"packages/catalyst-core/changelog.md",
|
|
1575
|
+
"docs/content/12-Migration/02-Version-Upgrades.md"
|
|
1576
|
+
]
|
|
1460
1577
|
}
|
|
1461
1578
|
]
|
|
@@ -50,3 +50,62 @@ describe("check_config: on-device AI", () => {
|
|
|
50
50
|
expect(aiFields(r)).toEqual([])
|
|
51
51
|
})
|
|
52
52
|
})
|
|
53
|
+
|
|
54
|
+
function makeSplashProject(config: unknown, files: string[] = []): string {
|
|
55
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "mcp-cfg-"))
|
|
56
|
+
fs.mkdirSync(path.join(root, "config"))
|
|
57
|
+
fs.writeFileSync(path.join(root, "config", "config.json"), JSON.stringify(config))
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
fs.mkdirSync(path.dirname(path.join(root, f)), { recursive: true })
|
|
60
|
+
fs.writeFileSync(path.join(root, f), "")
|
|
61
|
+
}
|
|
62
|
+
return root
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const findings = (r: ReturnType<typeof handle_check_config>) => [...r.issues, ...r.warnings, ...r.passed]
|
|
66
|
+
|
|
67
|
+
describe("check_config: splash screen", () => {
|
|
68
|
+
it("reads splashScreen from WEBVIEW_CONFIG and treats missing images as fine", () => {
|
|
69
|
+
const r = handle_check_config({
|
|
70
|
+
project_path: makeSplashProject({
|
|
71
|
+
WEBVIEW_CONFIG: { splashScreen: { backgroundColor: "#fff" } },
|
|
72
|
+
}),
|
|
73
|
+
})
|
|
74
|
+
expect(r.passed.some((p) => p.field === "WEBVIEW_CONFIG.splashScreen")).toBe(true)
|
|
75
|
+
expect(r.issues.some((i) => /splash/i.test(i.field))).toBe(false)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("flags a top-level splashScreen as misplaced", () => {
|
|
79
|
+
const r = handle_check_config({
|
|
80
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: {}, splashScreen: { duration: 1000 } }),
|
|
81
|
+
})
|
|
82
|
+
expect(
|
|
83
|
+
r.warnings.some((w) => w.field === "WEBVIEW_CONFIG.splashScreen" && /top level/.test(w.message!))
|
|
84
|
+
).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("recognises public/splashscreen.html", () => {
|
|
88
|
+
const r = handle_check_config({
|
|
89
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { splashScreen: { minDuration: 1200 } } }, [
|
|
90
|
+
"public/splashscreen.html",
|
|
91
|
+
]),
|
|
92
|
+
})
|
|
93
|
+
expect(r.passed.some((p) => p.field === "public/splashscreen.html")).toBe(true)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it("warns on an invalid minDuration", () => {
|
|
97
|
+
for (const minDuration of [-1, "1200"]) {
|
|
98
|
+
const r = handle_check_config({
|
|
99
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { splashScreen: { minDuration } } }),
|
|
100
|
+
})
|
|
101
|
+
expect(r.warnings.some((w) => w.field === "WEBVIEW_CONFIG.splashScreen.minDuration")).toBe(true)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("accepts a valid minDuration silently", () => {
|
|
106
|
+
const r = handle_check_config({
|
|
107
|
+
project_path: makeSplashProject({ WEBVIEW_CONFIG: { splashScreen: { minDuration: 0 } } }),
|
|
108
|
+
})
|
|
109
|
+
expect(findings(r).some((f) => f.field === "WEBVIEW_CONFIG.splashScreen.minDuration")).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
})
|
package/mcp_v2/tools/build.js
CHANGED
|
@@ -23,7 +23,7 @@ const MASTER_FLOWS = {
|
|
|
23
23
|
step: 1,
|
|
24
24
|
cmd: "catalyst start",
|
|
25
25
|
label: "Start dev server",
|
|
26
|
-
detail: "Starts the Vite middleware-mode development server and Catalyst SSR server on NODE_SERVER_PORT. Hot reload and React Fast Refresh are enabled.",
|
|
26
|
+
detail: "Starts the Vite middleware-mode development server and Catalyst SSR server on NODE_SERVER_PORT. Hot reload and React Fast Refresh are enabled, including for JSX in .js files (1.0.2+). Since 1.0.2 the terminal shows a short header and one line per step; add --verbose for every step and the full tool output, or --debug for stacks and the environment.",
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
step: 2,
|
|
@@ -38,13 +38,13 @@ const MASTER_FLOWS = {
|
|
|
38
38
|
step: 1,
|
|
39
39
|
cmd: "catalyst build",
|
|
40
40
|
label: "Production build",
|
|
41
|
-
detail: "Runs parallel Vite client and SSR builds. Outputs client assets to build/client, the renderer to build/server, Vite manifests under build/.vite, and offline runtime files at the build root.",
|
|
41
|
+
detail: "Runs parallel Vite client and SSR builds. Outputs client assets to build/client, the renderer to build/server, Vite manifests under build/.vite, and offline runtime files at the build root. Since 1.0.2 it prints one line per step with timing and, on failure, the extracted errors with file, line and a hint; --verbose or --debug add the error code (BUNDLE-000), its docs link and the bundler's full output. It also writes <BUILD_OUTPUT_PATH>/.catalyst-build.json with the config values inlined into the bundles.",
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
step: 2,
|
|
45
45
|
cmd: "catalyst serve",
|
|
46
46
|
label: "Serve built app (local)",
|
|
47
|
-
detail: "Serves from build/ — same as production but local. Use for smoke-testing the build before deploy.",
|
|
47
|
+
detail: "Serves from build/ — same as production but local. Use for smoke-testing the build before deploy. Since 1.0.2 it exits if there is no build, and refuses a stale build (PREFLIGHT-022) when PUBLIC_STATIC_ASSET_URL, PUBLIC_STATIC_ASSET_PATH or a CLIENT_ENV_VARIABLES value changed in config/config.json after the build; rebuild, then serve. The app's own output passes through unchanged.",
|
|
48
48
|
},
|
|
49
49
|
],
|
|
50
50
|
|
|
@@ -101,7 +101,7 @@ const MASTER_FLOWS = {
|
|
|
101
101
|
step: 3,
|
|
102
102
|
cmd: "npm run buildApp:android",
|
|
103
103
|
label: "Android debug build",
|
|
104
|
-
detail: "Runs buildAppAndroid.js: validates ADB + emulator path from sdkPath, launches emulator (emulatorName), copies web assets to androidProject/, runs Gradle debug build, installs APK.",
|
|
104
|
+
detail: "Runs buildAppAndroid.js: validates ADB + emulator path from sdkPath, launches emulator (emulatorName), copies web assets to androidProject/, runs Gradle debug build, installs APK. Since 1.0.2 Gradle runs behind one progress line that shows the current task; on failure --verbose or --debug add the ANDROID-000 report with the full Gradle output.",
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
step: 4,
|
|
@@ -301,7 +301,7 @@ const MASTER_DIAGRAMS = {
|
|
|
301
301
|
"LOCAL_IP — override auto-detected IP for device builds",
|
|
302
302
|
"accessControl.allowedUrls — whitelist for outbound requests from WebView",
|
|
303
303
|
"googleSignIn.enabled / clientId / iosClientId",
|
|
304
|
-
"splashScreen — duration, backgroundColor,
|
|
304
|
+
"splashScreen — duration, minDuration, backgroundColor, image size; optional animated public/splashscreen.html (1.0.2+)",
|
|
305
305
|
"notifications.enabled — push notification permission prompt",
|
|
306
306
|
"android.buildOptimisation — enables build caching",
|
|
307
307
|
"android.keystoreConfig — signing for release builds",
|
|
@@ -760,7 +760,7 @@ function handle_get_architecture_diagram({ feature, symptom } = {}) {
|
|
|
760
760
|
project_context.push(`iOS configured: ${ctx.hasIos}`)
|
|
761
761
|
if (ctx.hasGoogleSignIn) project_context.push("googleSignIn: enabled")
|
|
762
762
|
const { readJson } = makeProjectHelpers(catalystRoot.dir)
|
|
763
|
-
const splash = (readJson("config/config.json") || {}).splashScreen
|
|
763
|
+
const splash = (readJson("config/config.json") || {}).WEBVIEW_CONFIG?.splashScreen
|
|
764
764
|
if (splash)
|
|
765
765
|
project_context.push(`splashScreen: configured (duration: ${splash.duration || "default"})`)
|
|
766
766
|
}
|
package/mcp_v2/tools/config.js
CHANGED
|
@@ -389,28 +389,45 @@ function handle_check_config({ project_path, platform = "both" } = {}) {
|
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
// ── splashScreen (
|
|
393
|
-
|
|
392
|
+
// ── splashScreen (inside WEBVIEW_CONFIG; the native builds read only that) ──
|
|
393
|
+
const splash = wc.splashScreen
|
|
394
|
+
const hasSplashHtml = fs.existsSync(path.join(root, "public", "splashscreen.html"))
|
|
395
|
+
if (!splash) {
|
|
394
396
|
warnings.push({
|
|
395
|
-
field: "splashScreen",
|
|
396
|
-
message:
|
|
397
|
-
|
|
397
|
+
field: "WEBVIEW_CONFIG.splashScreen",
|
|
398
|
+
message: config.splashScreen
|
|
399
|
+
? "splashScreen is at the top level of config/config.json, where the native builds never read it. Move it into WEBVIEW_CONFIG.splashScreen."
|
|
400
|
+
: `WEBVIEW_CONFIG.splashScreen not set, so the native splash screen is off${hasSplashHtml ? " and public/splashscreen.html is ignored" : ""}. Add WEBVIEW_CONFIG.splashScreen (e.g. { "backgroundColor": "#ffffff" }) to turn it on.`,
|
|
398
401
|
})
|
|
399
402
|
} else {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
403
|
+
// Images are optional: Android falls back to the launcher icon, iOS to a loader.
|
|
404
|
+
const found = (dir, exts) =>
|
|
405
|
+
exts
|
|
406
|
+
.map((ext) => `public/${dir}/splashscreen.${ext}`)
|
|
407
|
+
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - root is the local project root; f comes from a fixed candidate list.
|
|
408
|
+
.find((f) => fs.existsSync(path.join(root, f)))
|
|
409
|
+
const androidImage = found("android", ["png", "jpg", "jpeg", "gif", "bmp", "webp"])
|
|
410
|
+
const iosImage = found("ios", ["png", "jpg", "jpeg"])
|
|
411
|
+
passed.push({
|
|
412
|
+
field: "WEBVIEW_CONFIG.splashScreen",
|
|
413
|
+
note: `android image: ${androidImage || "none (launcher icon)"}, ios image: ${iosImage || "none (loader)"}`,
|
|
414
|
+
})
|
|
415
|
+
// Same rule as the iOS build (buildIos/config.js): anything else is ignored with a warning.
|
|
416
|
+
const { minDuration } = splash
|
|
417
|
+
if (
|
|
418
|
+
minDuration !== undefined &&
|
|
419
|
+
!(typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration >= 0)
|
|
420
|
+
) {
|
|
421
|
+
warnings.push({
|
|
422
|
+
field: "WEBVIEW_CONFIG.splashScreen.minDuration",
|
|
423
|
+
message: `splashScreen.minDuration must be a non-negative number of milliseconds. Got ${JSON.stringify(minDuration)}; the build ignores it and uses 0.`,
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
if (hasSplashHtml) {
|
|
427
|
+
passed.push({
|
|
428
|
+
field: "public/splashscreen.html",
|
|
429
|
+
note: `HTML splash on (catalyst-core 1.0.2+): shown after the native image until the first page loads, for at least minDuration (${minDuration || 0} ms), at most 10 s; duration is ignored`,
|
|
411
430
|
})
|
|
412
|
-
} else {
|
|
413
|
-
passed.push({ field: "splashScreen", note: "config and both asset files present" })
|
|
414
431
|
}
|
|
415
432
|
}
|
|
416
433
|
|
|
@@ -390,23 +390,17 @@ function handle_get_conversion_status({ project_path, include_not_applicable = f
|
|
|
390
390
|
native_risk: "No config — splash screen config cannot be read.",
|
|
391
391
|
reason: "config/config.json not found",
|
|
392
392
|
}
|
|
393
|
-
if (!
|
|
393
|
+
if (!webviewConfig || !webviewConfig.splashScreen)
|
|
394
394
|
return {
|
|
395
395
|
status: "gap",
|
|
396
396
|
native_risk:
|
|
397
|
-
"Without splashScreen
|
|
398
|
-
reason:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
(f) => !fileExists(f)
|
|
402
|
-
)
|
|
403
|
-
if (missing.length)
|
|
404
|
-
return {
|
|
405
|
-
status: "gap",
|
|
406
|
-
native_risk:
|
|
407
|
-
"Splash config exists but asset files missing — build will fail when packaging the native app.",
|
|
408
|
-
reason: `Splash asset files missing: ${missing.join(", ")}`,
|
|
397
|
+
"Without WEBVIEW_CONFIG.splashScreen the Catalyst splash is skipped and the app goes straight to the WebView while JS loads. Not a crash, but poor UX on slow devices.",
|
|
398
|
+
reason: config.splashScreen
|
|
399
|
+
? "splashScreen is at the top level of config/config.json; the native builds read only WEBVIEW_CONFIG.splashScreen"
|
|
400
|
+
: "WEBVIEW_CONFIG.splashScreen missing from config/config.json",
|
|
409
401
|
}
|
|
402
|
+
// Images are optional (Android falls back to the launcher icon, iOS to a loader), so a
|
|
403
|
+
// missing image is not a gap.
|
|
410
404
|
return { status: "completed" }
|
|
411
405
|
},
|
|
412
406
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "catalyst-core",
|
|
3
|
-
"version": "1.0.2
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.jsx",
|
|
6
6
|
"description": "Web framework that provides great performance out of the box",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"scripts": {
|
|
57
57
|
"lint": "eslint .",
|
|
58
58
|
"lint-staged": "lint-staged",
|
|
59
|
-
"test:unit": "node --test \"test/**/*.test.js\"",
|
|
59
|
+
"test:unit": "node --test \"test/**/*.test.js\" \"test/**/*.test.cjs\"",
|
|
60
60
|
"typecheck": "tsc --noEmit",
|
|
61
61
|
"prettify": "prettier . --write",
|
|
62
62
|
"prepare": "node scripts/safe-rimraf.cjs ./dist && node scripts/build-dist.mjs && cp -r ./src/native ./dist/native && babel src/native --out-dir dist/native --config-file ./babel.native.config.cjs --extensions '.js,.jsx,.ts,.tsx' --ignore '**/.build/**' && node scripts/strip-ts-sources.mjs ./dist/native && babel src/sentry.js --out-file dist/sentry.cjs --config-file ./babel.native.config.cjs && tsc -p tsconfig.declarations.json && node ./mcp_v2/scripts/copyErrorsIndex.cjs",
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
"react-redux": "^9.2.0",
|
|
84
84
|
"react-refresh": "^0.14.0",
|
|
85
85
|
"redux": "^4.2.1",
|
|
86
|
-
"sanitize-html": "^2.
|
|
86
|
+
"sanitize-html": "^2.18.1",
|
|
87
87
|
"sass": "^1.89.2",
|
|
88
88
|
"ua-parser-js": "^1.0.37",
|
|
89
|
-
"vite": "^6.4.
|
|
89
|
+
"vite": "^6.4.5",
|
|
90
90
|
"vite-plugin-svgr": "^5.0.0",
|
|
91
91
|
"winston": "^3.11.0",
|
|
92
92
|
"winston-daily-rotate-file": "^5.0.0"
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"eslint-plugin-security": "^3.0.0",
|
|
126
126
|
"husky": "^9.0.11",
|
|
127
127
|
"jsdom": "30.0.1",
|
|
128
|
-
"lint-staged": "^
|
|
128
|
+
"lint-staged": "^17.6.0",
|
|
129
129
|
"prettier": "^3.2.5",
|
|
130
130
|
"react-router": "^7.18.2",
|
|
131
131
|
"typescript": "^7.0.2",
|