catalyst-core 0.1.0-canary.9 → 0.2.0-beta.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/README.md +1 -1
- package/bin/catalyst.js +8 -1
- package/changelog.md +6 -0
- package/dist/native/androidProject/app/build.gradle.kts +34 -8
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +270 -78
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +34 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +149 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/OfflineCacheService.kt +374 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +156 -39
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +7 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +6 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +253 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +16 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +7 -3
- package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +7 -2
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +139 -0
- package/dist/native/androidSetup.js +1 -0
- package/dist/native/bridge/WebBridge.js +5 -5
- package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
- package/dist/native/bridge/hooks/index.js +1 -0
- package/dist/native/bridge/hooks/useCamera.js +9 -0
- package/dist/native/bridge/hooks/useDataProtection.js +2 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
- package/dist/native/bridge/hooks/useFilePicker.js +2 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
- package/dist/native/bridge/hooks/useIntent.js +1 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
- package/dist/native/bridge/hooks/useNotification.js +2 -0
- package/dist/native/bridge/hooks/useSafeArea.js +2 -0
- package/dist/native/bridge/hooks/useVideoStream.js +6 -0
- package/dist/native/bridge/hooks/utils.js +1 -0
- package/dist/native/bridge/hooks.js +1 -137
- package/dist/native/bridge/useBaseHook.js +12 -7
- package/dist/native/bridge/utils/NativeBridge.js +41 -6
- package/dist/native/buildAndroid/assets.js +52 -0
- package/dist/native/buildAndroid/build.js +19 -0
- package/dist/native/buildAndroid/config.js +1 -0
- package/dist/native/buildAndroid/index.js +1 -0
- package/dist/native/buildAppAndroid.js +2 -150
- package/dist/native/buildAppIos.js +2 -312
- package/dist/native/buildIos/assets.js +8 -0
- package/dist/native/buildIos/build.js +40 -0
- package/dist/native/buildIos/config.js +71 -0
- package/dist/native/buildIos/index.js +6 -0
- package/dist/native/buildIos/plugins.js +45 -0
- package/dist/native/internalPluginUtils.js +7 -0
- package/dist/native/iosnativeWebView/Package.resolved +36 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
- package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +5 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +6 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +364 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +39 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineCacheService.swift +683 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineURLSchemeHandler.swift +94 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +19 -6
- package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +227 -4
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +14 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +53 -8
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +88 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +30 -11
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +1 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +4 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
- package/dist/native/plugin-bridge/PluginBridge.js +1 -0
- package/dist/native/pluginComposerAndroid.js +28 -0
- package/dist/native/pluginComposerIos.js +16 -0
- package/dist/native/renameAndroidProject.js +5 -5
- package/dist/native/setupEmulatorIos.js +6 -4
- package/dist/native/setupServer.js +2 -3
- package/dist/native/utils.js +4 -1
- package/dist/offline/catalyst-sw.js +1 -0
- package/dist/offline/registerServiceWorker.js +1 -0
- package/dist/otel.js +70 -2
- package/dist/router/ClientRouter.js +1 -1
- package/dist/scripts/build.js +1 -1
- package/dist/scripts/cleanCache.js +1 -0
- package/dist/scripts/devBuild.js +1 -1
- package/dist/scripts/devServe.js +1 -1
- package/dist/scripts/generateOfflineManifest.js +1 -0
- package/dist/scripts/loadEnvironmentVariables.js +1 -2
- package/dist/scripts/plugins.js +1 -0
- package/dist/scripts/registerAliases.js +6 -2
- package/dist/scripts/scriptUtils.js +2 -1
- package/dist/scripts/serve.js +2 -1
- package/dist/scripts/start.js +7 -2
- package/dist/server/expressServer.js +4 -8
- package/dist/server/renderer/document/Body.js +4 -4
- package/dist/server/renderer/document/Head.js +2 -2
- package/dist/server/renderer/extract.js +31 -27
- package/dist/server/renderer/handler.js +47 -24
- package/dist/server/renderer/index.js +1 -1
- package/dist/server/startServer.js +57 -7
- package/dist/server/utils/userAgentUtil.js +9 -3
- package/dist/server/utils/validator.js +1 -1
- package/dist/webpack/babel-plugins/remove-client.plugin.js +2 -2
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +2 -2
- package/dist/webpack/babel.config.client.js +1 -1
- package/dist/webpack/babel.config.ssr.js +1 -1
- package/dist/webpack/base.babel.js +7 -8
- package/dist/webpack/development.client.babel.js +7 -16
- package/dist/webpack/production.client.babel.js +1 -1
- package/dist/webpack/production.ssr.babel.js +5 -1
- package/mcp_v2/knowledge-base.json +206 -100
- package/mcp_v2/lib/helpers.js +42 -8
- package/mcp_v2/lib/seed.js +88 -0
- package/mcp_v2/mcp.js +150 -4
- package/mcp_v2/setup.js +30 -193
- package/mcp_v2/tools/build.js +1 -1
- package/mcp_v2/tools/config.js +16 -4
- package/mcp_v2/tools/conversion.js +11 -11
- package/mcp_v2/tools/github.js +1117 -0
- package/mcp_v2/tools/knowledge.js +16 -3
- package/mcp_v2/tools/sync.js +179 -10
- package/mcp_v2/tools/tasks.js +13 -4
- package/package.json +20 -8
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{
|
|
3
3
|
"section": "framework_identity",
|
|
4
4
|
"title": "Catalyst Framework Philosophy",
|
|
5
|
-
"content": "Catalyst is a WebView-based universal app framework enabling a single codebase to ship natively on Android/iOS and on web. Zero user intervention required. Uses native WebView (WebKit on iOS, Chromium on Android) which owns its own JS engine
|
|
5
|
+
"content": "Catalyst is a WebView-based universal app framework enabling a single codebase to ship natively on Android/iOS and on web. Zero user intervention required. Uses native WebView (WebKit on iOS, Chromium on Android) which owns its own JS engine \u2014 not React Native, not React Native Web.",
|
|
6
6
|
"layer": "Component",
|
|
7
7
|
"source": "static",
|
|
8
8
|
"tags": ["core", "architecture", "universal"]
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"section": "framework_identity",
|
|
12
12
|
"title": "Not React Native",
|
|
13
|
-
"content": "Catalyst is fundamentally incompatible with React Native (JSI, TurboModules). Cannot adopt JSI without replacing WebView entirely
|
|
13
|
+
"content": "Catalyst is fundamentally incompatible with React Native (JSI, TurboModules). Cannot adopt JSI without replacing WebView entirely \u2014 would require rebuilding as React Native instead. JSI targets React Native's single-threaded JS runtime, not a WebView JS engine.",
|
|
14
14
|
"layer": "Component",
|
|
15
15
|
"source": "static",
|
|
16
16
|
"tags": ["architecture", "constraints"]
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
{
|
|
51
51
|
"section": "transport_architecture",
|
|
52
52
|
"title": "Operation Type Selection",
|
|
53
|
-
"content": "Five operation types determine transport choice: 1) Fire&Forget (bridge), 2) Simple Async (bridge), 3) Event-Driven (bridge), 4) Long-Running (localhost server), 5) Size-Dependent (hybrid selection based on payload). Size thresholds:
|
|
53
|
+
"content": "Five operation types determine transport choice: 1) Fire&Forget (bridge), 2) Simple Async (bridge), 3) Event-Driven (bridge), 4) Long-Running (localhost server), 5) Size-Dependent (hybrid selection based on payload). Size thresholds: \u22642MB = direct intent. 2MB-50MB = content provider or localhost server. >50MB = chunked streaming.",
|
|
54
54
|
"layer": "Runtime",
|
|
55
55
|
"source": "static",
|
|
56
56
|
"tags": ["transport", "operations"]
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
{
|
|
91
91
|
"section": "bridge_architecture",
|
|
92
92
|
"title": "Android Bridge Flow",
|
|
93
|
-
"content": "JS
|
|
93
|
+
"content": "JS \u2192 window.NativeBridge[command](data) \u2192 Kotlin @JavascriptInterface method \u2192 native work \u2192 webView.evaluateJavascript(jsCode) \u2192 JS callback. Command and data passed as JSON. Response injected back as JS code execution.",
|
|
94
94
|
"layer": "Bridge",
|
|
95
95
|
"source": "static",
|
|
96
96
|
"tags": ["android", "bridge", "ipc"]
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
{
|
|
99
99
|
"section": "bridge_architecture",
|
|
100
100
|
"title": "iOS Bridge Flow",
|
|
101
|
-
"content": "JS
|
|
101
|
+
"content": "JS \u2192 webkit.messageHandlers.NativeBridge.postMessage(msg) \u2192 WKScriptMessageHandler Swift handler \u2192 native work \u2192 evaluateJavaScript(jsCode) \u2192 JS callback. Message passed as JSON object. Response injected as JS code execution.",
|
|
102
102
|
"layer": "Bridge",
|
|
103
103
|
"source": "static",
|
|
104
104
|
"tags": ["ios", "bridge", "ipc"]
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
{
|
|
131
131
|
"section": "bridge_architecture",
|
|
132
132
|
"title": "isNative() Runtime Check Bug",
|
|
133
|
-
"content": "Bug: isNative() constructed at module load (SSR), window.nativeBridge absent
|
|
133
|
+
"content": "Bug: isNative() constructed at module load (SSR), window.nativeBridge absent \u2192 nativeEnvironment=false permanently. Never recovers in browser. Fix: check window.nativeBridge and window.webkit?.messageHandlers?.NativeBridge lazily at call time, not at module init.",
|
|
134
134
|
"layer": "Bridge",
|
|
135
135
|
"source": "static",
|
|
136
136
|
"tags": ["bridge", "bug", "ssr"]
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
{
|
|
179
179
|
"section": "build_system",
|
|
180
180
|
"title": "Build NPM Scripts",
|
|
181
|
-
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild
|
|
181
|
+
"content": "All catalyst npm scripts to add to package.json scripts block (each maps to: catalyst <scriptName>): devBuild \u2014 dev web build. devServe \u2014 dev server. buildApp \u2014 builds native app (both platforms). buildApp:android \u2014 Android native app build. buildApp:ios \u2014 iOS native app build. buildApp:android:release \u2014 Android release build. buildApp:ios:release \u2014 iOS release build. setupEmulator \u2014 sets up both emulators. setupEmulator:android \u2014 sets up Android AVD. setupEmulator:ios \u2014 sets up iOS simulator. NOTE: build:android / build:android:release / build:ios do NOT exist in catalyst \u2014 common mistake. The correct commands are buildApp:android and buildApp:ios. Missing any script = that npm run command fails.",
|
|
182
182
|
"layer": "Build",
|
|
183
183
|
"source": "static",
|
|
184
184
|
"tags": ["build", "scripts", "package.json", "native"]
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
{
|
|
219
219
|
"section": "security",
|
|
220
220
|
"title": "FLAG_SECURE Android Behavior",
|
|
221
|
-
"content": "setScreenSecure(true) sets FLAG_SECURE on WebView. Shows black/blank screenshot in Android recents. This is CORRECT behavior, not a bug. Emulators do NOT enforce FLAG_SECURE in recents
|
|
221
|
+
"content": "setScreenSecure(true) sets FLAG_SECURE on WebView. Shows black/blank screenshot in Android recents. This is CORRECT behavior, not a bug. Emulators do NOT enforce FLAG_SECURE in recents \u2014 must test on real device to verify.",
|
|
222
222
|
"layer": "Runtime",
|
|
223
223
|
"source": "static",
|
|
224
224
|
"tags": ["android", "security", "flag_secure"]
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
{
|
|
235
235
|
"section": "security",
|
|
236
236
|
"title": "clearWebData Behavior",
|
|
237
|
-
"content": "clearWebData does NOT clear WebCacheManager/CacheManager by default
|
|
237
|
+
"content": "clearWebData does NOT clear WebCacheManager/CacheManager by default \u2014 these are custom native disk+memory caches separate from WebView built-in cache (cookies, localStorage, etc). Both must be explicitly cleared via separate native calls.",
|
|
238
238
|
"layer": "Runtime",
|
|
239
239
|
"source": "static",
|
|
240
240
|
"tags": ["security", "cache", "clearing"]
|
|
@@ -242,7 +242,7 @@
|
|
|
242
242
|
{
|
|
243
243
|
"section": "security",
|
|
244
244
|
"title": "iOS clearWebData Implementation",
|
|
245
|
-
"content": "WKWebsiteDataStore clearing must wrap completion in DispatchQueue.main.async. Confirmed working in production. Note: Safari Web Inspector does NOT auto-refresh storage panel after clearWebData
|
|
245
|
+
"content": "WKWebsiteDataStore clearing must wrap completion in DispatchQueue.main.async. Confirmed working in production. Note: Safari Web Inspector does NOT auto-refresh storage panel after clearWebData \u2014 misleading but not a bug.",
|
|
246
246
|
"layer": "Runtime",
|
|
247
247
|
"source": "static",
|
|
248
248
|
"tags": ["ios", "security", "clearing"]
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
{
|
|
267
267
|
"section": "security",
|
|
268
268
|
"title": "Security Check Flow",
|
|
269
|
-
"content": "App Launch
|
|
269
|
+
"content": "App Launch \u2192 MainActivity.onCreate() \u2192 SecurityCheckScheduler.initialize() \u2192 background coroutine \u2192 SecurityCheckManager.performSecurityChecks() \u2192 rooting + emulator + Frida checks \u2192 callback \u2192 SecurityAlertHandler \u2192 recommendation returned to JS.",
|
|
270
270
|
"layer": "Runtime",
|
|
271
271
|
"source": "static",
|
|
272
272
|
"tags": ["android", "security", "flow"]
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
{
|
|
299
299
|
"section": "security",
|
|
300
300
|
"title": "Frida Detection Port Scan",
|
|
301
|
-
"content": "Scans ports 27042, 27043 for Frida server. 4 second latency acceptable
|
|
301
|
+
"content": "Scans ports 27042, 27043 for Frida server. 4 second latency acceptable \u2014 runs on background coroutine while WebView already showing on clean devices.",
|
|
302
302
|
"layer": "Runtime",
|
|
303
303
|
"source": "static",
|
|
304
304
|
"tags": ["android", "security", "frida"]
|
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
{
|
|
331
331
|
"section": "caching",
|
|
332
332
|
"title": "Cache Pattern Bug and Fix",
|
|
333
|
-
"content": "Bug: cache pattern matching matched against full URLs instead of filenames
|
|
333
|
+
"content": "Bug: cache pattern matching matched against full URLs instead of filenames \u2192 only 7/64 files cached (9% hit rate). Fix: extract filename from URL before matching against cachePattern (*.css, *.js, etc). After fix: 100% cache hit rate.",
|
|
334
334
|
"layer": "Runtime",
|
|
335
335
|
"source": "static",
|
|
336
336
|
"tags": ["caching", "bug", "fix"]
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
{
|
|
347
347
|
"section": "known_errors",
|
|
348
348
|
"title": "Silent Intent Failure on Size Exceeded",
|
|
349
|
-
"content": "Symptom: file open/share silently does nothing. Cause: Intent payload >2MB
|
|
349
|
+
"content": "Symptom: file open/share silently does nothing. Cause: Intent payload >2MB \u2014 Android drops it without error callback or exception. Fix: validate file size before bridge dispatch, use content provider or localhost server for files >2MB.",
|
|
350
350
|
"layer": "Runtime",
|
|
351
351
|
"source": "static",
|
|
352
352
|
"tags": ["android", "error", "intent"]
|
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
{
|
|
355
355
|
"section": "known_errors",
|
|
356
356
|
"title": "isNative() Always Returns False (SSR)",
|
|
357
|
-
"content": "Symptom: bridge hooks do nothing in browser, native features not available. Cause: NativeBridgeUtil constructed at module load during SSR
|
|
357
|
+
"content": "Symptom: bridge hooks do nothing in browser, native features not available. Cause: NativeBridgeUtil constructed at module load during SSR \u2014 window.NativeBridge absent at that point, isNativeEnvironment=false permanently. Fix: check window.NativeBridge and window.webkit?.messageHandlers?.NativeBridge lazily at hook call time, not at module init.",
|
|
358
358
|
"layer": "Bridge",
|
|
359
359
|
"source": "static",
|
|
360
360
|
"tags": ["error", "ssr", "bridge", "bug"]
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
{
|
|
363
363
|
"section": "known_errors",
|
|
364
364
|
"title": "Cache Hit Rate Near Zero",
|
|
365
|
-
"content": "Symptom: most static assets (CSS, JS, images) load from network every time despite cachePattern being configured. Cause: cache pattern matching against full URL instead of filename
|
|
365
|
+
"content": "Symptom: most static assets (CSS, JS, images) load from network every time despite cachePattern being configured. Cause: cache pattern matching against full URL instead of filename \u2014 URL contains path + query params that don't match *.css etc. Fix: extract filename from URL before pattern matching.",
|
|
366
366
|
"layer": "Runtime",
|
|
367
367
|
"source": "static",
|
|
368
368
|
"tags": ["error", "caching", "android", "ios"]
|
|
@@ -370,7 +370,7 @@
|
|
|
370
370
|
{
|
|
371
371
|
"section": "known_errors",
|
|
372
372
|
"title": "clearWebData Doesn't Clear Native Cache",
|
|
373
|
-
"content": "Symptom: clearWebData called but cached assets still serve from disk. Cause: clearWebData only clears WebView built-in storage (cookies, localStorage)
|
|
373
|
+
"content": "Symptom: clearWebData called but cached assets still serve from disk. Cause: clearWebData only clears WebView built-in storage (cookies, localStorage) \u2014 not WebCacheManager (Android) or CacheManager (iOS) which are separate custom caches. Fix: call WebCacheManager.clearAll() on Android and CacheManager.clearCache() on iOS in addition to clearWebData.",
|
|
374
374
|
"layer": "Runtime",
|
|
375
375
|
"source": "static",
|
|
376
376
|
"tags": ["error", "caching", "security", "clearing"]
|
|
@@ -378,7 +378,7 @@
|
|
|
378
378
|
{
|
|
379
379
|
"section": "known_errors",
|
|
380
380
|
"title": "Localhost Server Transport Not Working",
|
|
381
|
-
"content": "Symptom: file operations, downloads, or progress-tracked operations fail silently or 404. Cause: accessControl.allowedUrls is defined but doesn't include http://localhost:*
|
|
381
|
+
"content": "Symptom: file operations, downloads, or progress-tracked operations fail silently or 404. Cause: accessControl.allowedUrls is defined but doesn't include http://localhost:* \u2014 all localhost requests blocked. Fix: add http://localhost:* and https://localhost:* to accessControl.allowedUrls.",
|
|
382
382
|
"layer": "Config",
|
|
383
383
|
"source": "static",
|
|
384
384
|
"tags": ["error", "transport", "server", "config"]
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
{
|
|
395
395
|
"section": "known_errors",
|
|
396
396
|
"title": "FLAG_SECURE Not Working on Emulator",
|
|
397
|
-
"content": "Symptom: setScreenSecure(true) called but screenshots still work, recents shows live preview. Cause: emulators do NOT enforce FLAG_SECURE
|
|
397
|
+
"content": "Symptom: setScreenSecure(true) called but screenshots still work, recents shows live preview. Cause: emulators do NOT enforce FLAG_SECURE \u2014 this is expected Android emulator behavior, not a bug. Fix: test FLAG_SECURE behavior on real device only.",
|
|
398
398
|
"layer": "Runtime",
|
|
399
399
|
"source": "static",
|
|
400
400
|
"tags": ["error", "android", "security", "emulator"]
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
{
|
|
403
403
|
"section": "known_errors",
|
|
404
404
|
"title": "iOS clearWebData Appears Not Working",
|
|
405
|
-
"content": "Symptom: clearWebData called on iOS, Safari Web Inspector still shows localStorage data. Cause: Safari Web Inspector does NOT auto-refresh its storage panel
|
|
405
|
+
"content": "Symptom: clearWebData called on iOS, Safari Web Inspector still shows localStorage data. Cause: Safari Web Inspector does NOT auto-refresh its storage panel \u2014 the data is actually cleared. Fix: manually refresh the storage panel in Web Inspector to confirm.",
|
|
406
406
|
"layer": "Runtime",
|
|
407
407
|
"source": "static",
|
|
408
408
|
"tags": ["error", "ios", "security", "clearing"]
|
|
@@ -418,7 +418,7 @@
|
|
|
418
418
|
{
|
|
419
419
|
"section": "webview_config",
|
|
420
420
|
"title": "Real WEBVIEW_CONFIG Example",
|
|
421
|
-
"content": "Full WEBVIEW_CONFIG example (as of canary.19+): { port: '3005', LOCAL_IP: '192.168.0.11', useHttps: false, appInfo: 'android-5Feb2026-v2.1.0', cachePattern: '*.css', accessControl: { enabled: false, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] }, android: { appName: 'MyApp', packageName: 'com.example.app', buildType: 'debug', sdkPath: '/Users/user/Android/', emulatorName: 'Pixel_5_API_30', cachePattern: '*.css,*.js,*.png', security: { mode: 'custom', allowBackup: true } }, ios: { appBundleId: 'com.example.app', appName: 'MyApp', buildType: 'debug', simulatorName: 'iPhone 17 Pro', cachePattern: '*.css,*.js', accessControl: { enabled: true, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] } }, splashScreen: { backgroundColor: '#ffffff', duration: 2000, imageWidth: 400, imageHeight: 200, cornerRadius: 20 }, notifications: { enabled:
|
|
421
|
+
"content": "Full WEBVIEW_CONFIG example (as of canary.19+): { port: '3005', LOCAL_IP: '192.168.0.11', useHttps: false, appInfo: 'android-5Feb2026-v2.1.0', cachePattern: '*.css', accessControl: { enabled: false, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] }, android: { appName: 'MyApp', packageName: 'com.example.app', buildType: 'debug', sdkPath: '/Users/user/Android/', emulatorName: 'Pixel_5_API_30', cachePattern: '*.css,*.js,*.png', security: { mode: 'custom', allowBackup: true } }, ios: { appBundleId: 'com.example.app', appName: 'MyApp', buildType: 'debug', simulatorName: 'iPhone 17 Pro', cachePattern: '*.css,*.js', accessControl: { enabled: true, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*'] } }, splashScreen: { backgroundColor: '#ffffff', duration: 2000, imageWidth: 400, imageHeight: 200, cornerRadius: 20 }, notifications: { enabled: true, color: \"#007AFF\" } }. Note: splashScreen is INSIDE WEBVIEW_CONFIG, not at top-level config.json. Note canary.19 change: iOS has its OWN accessControl block separate from top-level accessControl.",
|
|
422
422
|
"layer": "Config",
|
|
423
423
|
"source": "extracted:config/config.json",
|
|
424
424
|
"tags": ["config", "example"]
|
|
@@ -426,7 +426,7 @@
|
|
|
426
426
|
{
|
|
427
427
|
"section": "config_structure",
|
|
428
428
|
"title": "Full config/config.json Schema",
|
|
429
|
-
"content": "config/config.json contains all runtime config. Required keys (enforced by validator.js at startup
|
|
429
|
+
"content": "config/config.json contains all runtime config. Required keys (enforced by validator.js at startup \u2014 missing any = server crash): NODE_SERVER_HOSTNAME (string), NODE_SERVER_PORT (number), WEBPACK_DEV_SERVER_HOSTNAME (string), WEBPACK_DEV_SERVER_PORT (number), BUILD_OUTPUT_PATH (string, e.g. 'build'), PUBLIC_STATIC_ASSET_PATH (string, e.g. '/assets/'), PUBLIC_STATIC_ASSET_URL (string, CDN base URL), CLIENT_ENV_VARIABLES (array of env var names to expose to client bundle), ANALYZE_BUNDLE (boolean). Optional top-level keys: API_URL (string, app convention not enforced), WEBVIEW_CONFIG (object, required for native builds \u2014 contains splashScreen, appInfo, android, ios, etc.). Common mistake: CLIENT_ENV_KEYS is WRONG \u2014 the actual key is CLIENT_ENV_VARIABLES. Common mistake: splashScreen is NOT a top-level key \u2014 it lives inside WEBVIEW_CONFIG. Example: { NODE_SERVER_HOSTNAME: 'localhost', NODE_SERVER_PORT: 3000, WEBPACK_DEV_SERVER_HOSTNAME: 'localhost', WEBPACK_DEV_SERVER_PORT: 3001, BUILD_OUTPUT_PATH: 'build', PUBLIC_STATIC_ASSET_PATH: '/assets/', PUBLIC_STATIC_ASSET_URL: 'https://cdn.example.com', CLIENT_ENV_VARIABLES: ['API_URL'], ANALYZE_BUNDLE: false }",
|
|
430
430
|
"layer": "Config",
|
|
431
431
|
"source": "extracted:src/scripts/validator.js",
|
|
432
432
|
"tags": ["config", "schema", "required-fields"]
|
|
@@ -434,7 +434,7 @@
|
|
|
434
434
|
{
|
|
435
435
|
"section": "config_structure",
|
|
436
436
|
"title": "WEBVIEW_CONFIG Required Fields",
|
|
437
|
-
"content": "WEBVIEW_CONFIG field reference (from buildAppAndroid.js + buildAppIos.js source):\n\nTOP-LEVEL (both platforms):\n- port (string, required)
|
|
437
|
+
"content": "WEBVIEW_CONFIG field reference (from buildAppAndroid.js + buildAppIos.js source):\n\nTOP-LEVEL (both platforms):\n- port (string, required) \u2014 web server port e.g. \"3005\"\n- LOCAL_IP (string, required) \u2014 machine LAN IP e.g. \"192.168.0.11\". NOT localhost \u2014 emulator cannot resolve it\n- appInfo (string, required) \u2014 build identifier e.g. \"android-5Feb2026-v2.1.0\". Missing = iOS build breaks\n- useHttps (boolean, optional) \u2014 default false\n- cachePattern (string, optional) \u2014 comma-separated globs e.g. \"*.css\"\n- accessControl.enabled (boolean, optional) \u2014 default false\n- accessControl.allowedUrls (array, required if enabled) \u2014 URL patterns\n\nANDROID (WEBVIEW_CONFIG.android):\n- sdkPath (string, REQUIRED) \u2014 abs path to Android SDK. Build exits immediately without it\n- emulatorName (string, REQUIRED for debug) \u2014 AVD name. Run: emulator -list-avds\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- packageName (string, optional) \u2014 e.g. com.company.app. Derived from appName if missing\n- buildType (string, optional) \u2014 \"debug\" or \"release\". Default \"debug\"\n- keystore or keystoreConfig (required for release buildType only)\n- buildOptimisation (boolean, optional) \u2014 default false\n- cachePattern (string, optional)\n\nIOS (WEBVIEW_CONFIG.ios):\n- appBundleId (string, optional) \u2014 default \"com.debug.webview\". Must set for distribution\n- appName (string, optional) \u2014 default \"Catalyst Application\"\n- simulatorName (string, optional) \u2014 auto-detected if missing. e.g. \"iPhone 17 Pro\"\n- buildType (string, optional, CASE-SENSITIVE) \u2014 \"Debug\" or \"Release\". NOT lowercase \u2014 common mistake\n- deviceUDID (string, optional) \u2014 physical device builds only\n- developmentTeam (string, required if deviceUDID set) \u2014 Apple Developer Team ID\n- cachePattern (string, optional)",
|
|
438
438
|
"layer": "Config",
|
|
439
439
|
"source": "static",
|
|
440
440
|
"tags": ["config", "WEBVIEW_CONFIG", "android", "ios", "required", "fields"]
|
|
@@ -442,7 +442,7 @@
|
|
|
442
442
|
{
|
|
443
443
|
"section": "config_structure",
|
|
444
444
|
"title": "splashScreen Config Structure",
|
|
445
|
-
"content": "splashScreen sits INSIDE WEBVIEW_CONFIG (not at top-level config.json). All fields are optional
|
|
445
|
+
"content": "splashScreen sits INSIDE WEBVIEW_CONFIG (not at top-level config.json). All fields are optional \u2014 omitting splashScreen entirely = native build proceeds but shows blank splash. Fields: backgroundColor (string hex, default '#ffffff'), duration (number ms, iOS only \u2014 controls how long splash shows), imageWidth (number px, default 120), imageHeight (number px, default 120), cornerRadius (number px, iOS only, default 20). 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 asset file = build proceeds with no splash image (no error). backgroundColor is applied to Android themes.xml (windowSplashScreenBackground). Pfizer example: splashScreen: { imageHeight: 200, imageWidth: 400 } (backgroundColor omits = defaults to white).",
|
|
446
446
|
"layer": "Config",
|
|
447
447
|
"source": "static",
|
|
448
448
|
"tags": ["splashscreen", "splash", "config", "webview_config", "android", "ios", "native", "assets"]
|
|
@@ -458,7 +458,7 @@
|
|
|
458
458
|
{
|
|
459
459
|
"section": "routing",
|
|
460
460
|
"title": "Catalyst Routing Stack",
|
|
461
|
-
"content": "@tata1mg/router wraps React Router v6. Routes defined in src/js/routes/index.js as flat or nested array. RouterDataProvider in src/js/routes/utils.js wraps the entire app
|
|
461
|
+
"content": "@tata1mg/router wraps React Router v6. Routes defined in src/js/routes/index.js as flat or nested array. RouterDataProvider in src/js/routes/utils.js wraps the entire app \u2014 required for data fetching to work. Without RouterDataProvider, serverFetcher/clientFetcher hooks silently return undefined.",
|
|
462
462
|
"layer": "Runtime",
|
|
463
463
|
"source": "static",
|
|
464
464
|
"tags": ["routing", "react-router", "required"]
|
|
@@ -490,7 +490,7 @@
|
|
|
490
490
|
{
|
|
491
491
|
"section": "data_fetching",
|
|
492
492
|
"title": "serverFetcher Pattern",
|
|
493
|
-
"content": "Static function attached to page component: HomePage.serverFetcher = async ({ params, searchParams, navigate }, { store }) => { const data = await fetchApi(); return data; }. Runs on server during SSR. Result available via useCurrentRouteData(). NEVER use useEffect for initial data load
|
|
493
|
+
"content": "Static function attached to page component: HomePage.serverFetcher = async ({ params, searchParams, navigate }, { store }) => { const data = await fetchApi(); return data; }. Runs on server during SSR. Result available via useCurrentRouteData(). NEVER use useEffect for initial data load \u2014 serverFetcher is the catalyst way.",
|
|
494
494
|
"layer": "Runtime",
|
|
495
495
|
"source": "static",
|
|
496
496
|
"tags": ["data-fetching", "ssr", "server-fetcher"]
|
|
@@ -498,7 +498,7 @@
|
|
|
498
498
|
{
|
|
499
499
|
"section": "data_fetching",
|
|
500
500
|
"title": "clientFetcher Pattern",
|
|
501
|
-
"content": "Static function attached to page component: HomePage.clientFetcher = async ({ params }, { store }, customArgs) => { ... }. Runs on client-side navigation (not initial SSR load). Receives customArgs for manual trigger. Complement to serverFetcher
|
|
501
|
+
"content": "Static function attached to page component: HomePage.clientFetcher = async ({ params }, { store }, customArgs) => { ... }. Runs on client-side navigation (not initial SSR load). Receives customArgs for manual trigger. Complement to serverFetcher \u2014 server handles initial load, client handles navigations.",
|
|
502
502
|
"layer": "Runtime",
|
|
503
503
|
"source": "static",
|
|
504
504
|
"tags": ["data-fetching", "csr", "client-fetcher"]
|
|
@@ -513,8 +513,8 @@
|
|
|
513
513
|
},
|
|
514
514
|
{
|
|
515
515
|
"section": "data_fetching",
|
|
516
|
-
"title": "Migration: useEffect
|
|
517
|
-
"content": "Before: const [data, setData] = useState(null); useEffect(() => { fetchApi().then(setData) }, []). After: attach static HomePage.serverFetcher = async (...) => fetchApi(), then const data = useCurrentRouteData() in component body. Every page's data layer must be rewritten this way
|
|
516
|
+
"title": "Migration: useEffect \u2192 serverFetcher",
|
|
517
|
+
"content": "Before: const [data, setData] = useState(null); useEffect(() => { fetchApi().then(setData) }, []). After: attach static HomePage.serverFetcher = async (...) => fetchApi(), then const data = useCurrentRouteData() in component body. Every page's data layer must be rewritten this way \u2014 there is no incremental path.",
|
|
518
518
|
"layer": "Runtime",
|
|
519
519
|
"source": "static",
|
|
520
520
|
"tags": ["data-fetching", "migration", "useEffect"]
|
|
@@ -561,39 +561,46 @@
|
|
|
561
561
|
},
|
|
562
562
|
{
|
|
563
563
|
"section": "native_hooks",
|
|
564
|
-
"title": "Native Hooks
|
|
565
|
-
"content": "All catalyst-core hooks share a standardized base interface: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError }. isNative is true when running inside the native shell (Android/iOS WebView). isWeb is true on browser.
|
|
564
|
+
"title": "Native Hooks \u2014 Overview & Common Interface",
|
|
565
|
+
"content": "All catalyst-core hooks share a standardized base interface: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError }. isNative is true when running inside the native shell (Android/iOS WebView). isWeb is true on browser. All return SSR-safe stubs when window is undefined. Import from catalyst-core/hooks.\n\nWEB FALLBACK SYSTEM: Hooks that implement browser fallbacks additionally expose: { webFallbackActive: boolean, webFallbackDisabled: boolean, setWebFallback: (enabled: boolean) => void }. webFallbackActive=true means the hook is currently running its browser fallback (not native bridge). setWebFallback(false) disables the built-in fallback at runtime \u2014 React re-runs effects and cleans up side effects automatically (e.g. stops MediaStream, removes event listeners). webFallbackDisabled=true means the hook returned an inert no-op shape (all actions are no-ops, data/error/loading are null/false) \u2014 consumer must render its own UI.\n\nDYNAMIC PROP SYNC: The webFallback prop syncs dynamically after mount in all hooks (both useBaseHook consumers and thin-state hooks) via useEffect. Passing a new webFallback prop value re-evaluates webFallbackActive without remounting the component.\n\nRULES OF HOOKS: All hooks (useState, useRef, useCallback, useEffect) are always called unconditionally at top level, regardless of webFallbackActive/webFallbackDisabled. Branching only happens in return statements.\n\nHooks with web fallbacks: useCamera, useFilePicker, useHapticFeedback, useVideoStream, useDataProtection, useCameraPermission, useNetworkStatus, useSafeArea, useDeviceInfo.",
|
|
566
566
|
"layer": "Runtime",
|
|
567
567
|
"source": "static",
|
|
568
|
-
"tags": [
|
|
568
|
+
"tags": [
|
|
569
|
+
"native-hooks",
|
|
570
|
+
"overview",
|
|
571
|
+
"base-interface",
|
|
572
|
+
"web-fallback",
|
|
573
|
+
"webFallbackActive",
|
|
574
|
+
"setWebFallback"
|
|
575
|
+
]
|
|
569
576
|
},
|
|
570
577
|
{
|
|
571
578
|
"section": "native_hooks",
|
|
572
|
-
"title": "useCamera
|
|
573
|
-
"content": "useCamera() returns: { data: { fileSrc, fileName, size, mimeType, transport }, loading, progress, error, isWeb, isNative, execute, clear, clearError, permission, takePhoto (=execute), photo (=data), isLoading (=loading), clearPhoto (=clear) }. Call execute()/takePhoto() to open camera. Data arrives via WebBridge callback ON_CAMERA_CAPTURE. transport: 'BRIDGE_BASE64' | 'FRAMEWORK_SERVER' | 'LEGACY'.",
|
|
579
|
+
"title": "useCamera \u2014 return shape",
|
|
580
|
+
"content": "useCamera() returns: { data: { fileSrc, fileName, size, mimeType, transport }, loading, progress, error, isWeb, isNative, execute, clear, clearError, permission, takePhoto (=execute), photo (=data), isLoading (=loading), clearPhoto (=clear), webFallbackActive, webFallbackDisabled, setWebFallback }. Call execute()/takePhoto() to open camera. Data arrives via WebBridge callback ON_CAMERA_CAPTURE. transport: 'BRIDGE_BASE64' | 'FRAMEWORK_SERVER' | 'LEGACY'.\n\nWEB FALLBACK: Uses <input type='file' accept='image/*' capture='environment'> \u2014 opens native camera on mobile browsers without getUserMedia. setWebFallback(false) disables the fallback; when disabled, takePhoto() is a no-op and webFallbackDisabled=true.",
|
|
574
581
|
"layer": "Runtime",
|
|
575
582
|
"source": "static",
|
|
576
|
-
"tags": ["native-hooks", "useCamera", "return-shape"]
|
|
583
|
+
"tags": ["native-hooks", "useCamera", "return-shape", "web-fallback"]
|
|
577
584
|
},
|
|
578
585
|
{
|
|
579
586
|
"section": "native_hooks",
|
|
580
|
-
"title": "useCameraPermission
|
|
581
|
-
"content": "useCameraPermission() returns: { permission: string|null, isLoading: boolean }. permission values: 'granted' | 'denied' | 'not_determined'. Automatically
|
|
587
|
+
"title": "useCameraPermission \u2014 return shape",
|
|
588
|
+
"content": "useCameraPermission() returns: { permission: string|null, isLoading: boolean, webFallbackActive, webFallbackDisabled, setWebFallback, request: () => Promise<void> }. permission values: 'granted' | 'denied' | 'not_determined'. Automatically queries permission state on mount via navigator.permissions API (does NOT prompt).\n\nrequest() (web only): calls getUserMedia({ video: true }) to trigger the browser permission dialog, immediately stops tracks, then re-reads permission state. Use a button labeled 'Request Permission' to invoke this.\n\nWEB FALLBACK: Uses navigator.permissions.query({ name: 'camera' }) on web. permission maps from PermissionState: 'granted'\u2192GRANTED, 'denied'\u2192DENIED, 'prompt'\u2192NOT_DETERMINED. Manages webFallback state locally (not via useBaseHook). webFallback prop syncs dynamically after mount. request() is a no-op on native.\n\nRules of Hooks: all hooks called unconditionally at top level \u2014 early return only happens AFTER all hook calls.",
|
|
582
589
|
"layer": "Runtime",
|
|
583
590
|
"source": "static",
|
|
584
|
-
"tags": ["native-hooks", "useCameraPermission", "return-shape"]
|
|
591
|
+
"tags": ["native-hooks", "useCameraPermission", "return-shape", "web-fallback"]
|
|
585
592
|
},
|
|
586
593
|
{
|
|
587
594
|
"section": "native_hooks",
|
|
588
|
-
"title": "useFilePicker
|
|
589
|
-
"content": "useFilePicker() returns: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError, getFileObject: async(index) => File, getAllFileObjects: async() => File[], canCreateFileObject: boolean, canCreateFileObjects: boolean[], selectedFile (=files[0]), selectedFiles, pickFile (=execute), isLoading, processingState, clearFile (=clear) }. Call execute(options)/pickFile(options) to open native file picker. options: { mimeType, multiple, maxFiles, maxFileSize }. getFileObject/getAllFileObjects convert native file data to JS File objects for FormData POST.",
|
|
595
|
+
"title": "useFilePicker \u2014 return shape",
|
|
596
|
+
"content": "useFilePicker() returns: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError, getFileObject: async(index) => File, getAllFileObjects: async() => File[], canCreateFileObject: boolean, canCreateFileObjects: boolean[], selectedFile (=files[0]), selectedFiles, pickFile (=execute), isLoading, processingState, clearFile (=clear), webFallbackActive, webFallbackDisabled, setWebFallback }. Call execute(options)/pickFile(options) to open native file picker. options: { mimeType, multiple, maxFiles, maxFileSize }. getFileObject/getAllFileObjects convert native file data to JS File objects for FormData POST.\n\nWEB FALLBACK: Uses <input type='file'> \u2014 browser native file picker. Files land in data as an array of File objects. setWebFallback(false) disables; when disabled, pickFile() is a no-op.",
|
|
590
597
|
"layer": "Runtime",
|
|
591
598
|
"source": "static",
|
|
592
|
-
"tags": ["native-hooks", "useFilePicker", "return-shape"]
|
|
599
|
+
"tags": ["native-hooks", "useFilePicker", "return-shape", "web-fallback"]
|
|
593
600
|
},
|
|
594
601
|
{
|
|
595
602
|
"section": "native_hooks",
|
|
596
|
-
"title": "useIntent
|
|
603
|
+
"title": "useIntent \u2014 return shape",
|
|
597
604
|
"content": "useIntent() returns: { data: { result, success }, loading, progress, error, isWeb, isNative, execute, clear, clearError, openFile (=execute), isLoading, processingState, success, reset (=clear) }. Call execute(fileUrl, mimeType)/openFile(fileUrl, mimeType) to open a file with an external native app. Use for opening PDFs, docs in native viewers.",
|
|
598
605
|
"layer": "Runtime",
|
|
599
606
|
"source": "static",
|
|
@@ -601,31 +608,31 @@
|
|
|
601
608
|
},
|
|
602
609
|
{
|
|
603
610
|
"section": "native_hooks",
|
|
604
|
-
"title": "useGoogleSignIn
|
|
605
|
-
"content": "useGoogleSignIn(defaultOptions?) returns: { data: { idToken, ...user }, loading, progress, error, isWeb, isNative, signIn (=execute), execute, clear, clearError }. Call signIn(options)/execute(options) to trigger Google sign-in. options merged with defaultOptions. Requires google-services.json (Android) / GoogleService-Info.plist (iOS). On web: isNative false.",
|
|
611
|
+
"title": "useGoogleSignIn \u2014 return shape",
|
|
612
|
+
"content": "useGoogleSignIn(defaultOptions?) returns: { data: { idToken, ...user }, loading, progress, error, isWeb, isNative, signIn (=execute), execute, clear, clearError }. Call signIn(options)/execute(options) to trigger Google sign-in. options merged with defaultOptions. Requires: (1) WEBVIEW_CONFIG.googleSignIn.enabled=true in config.json \u2014 without this NativeBridge returns ON_GOOGLE_SIGN_IN_ERROR: \"Google Sign-In is disabled in config\". (2) WEBVIEW_CONFIG.googleSignIn.clientId (your OAuth web client ID from Google Cloud Console) \u2014 can also be passed per-call via signIn({clientId}). (3) google-services.json (Android) / GoogleService-Info.plist (iOS). On web: isNative false.",
|
|
606
613
|
"layer": "Runtime",
|
|
607
614
|
"source": "static",
|
|
608
615
|
"tags": ["native-hooks", "useGoogleSignIn", "return-shape"]
|
|
609
616
|
},
|
|
610
617
|
{
|
|
611
618
|
"section": "native_hooks",
|
|
612
|
-
"title": "useHapticFeedback
|
|
613
|
-
"content": "useHapticFeedback() returns: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError, trigger (=execute), triggerHaptic (=execute), light/medium/heavy/success/warning/errorHaptic/selection/impact (shortcuts), capabilities: { isSupported, availableTypes, platform }, isSupported, isAvailable (=isSupported), availableTypes, HAPTIC_TYPES }. Call execute(type)/trigger(type). Types: 'light'|'medium'|'heavy'|'success'|'warning'|'error'|'selection'|'impact'.
|
|
619
|
+
"title": "useHapticFeedback \u2014 return shape",
|
|
620
|
+
"content": "useHapticFeedback() returns: { data, loading, progress, error, isWeb, isNative, execute, clear, clearError, trigger (=execute), triggerHaptic (=execute), light/medium/heavy/success/warning/errorHaptic/selection/impact (shortcuts), capabilities: { isSupported, availableTypes, platform }, isSupported, isAvailable (=isSupported), availableTypes, HAPTIC_TYPES, webFallbackActive, webFallbackDisabled, setWebFallback }. Call execute(type)/trigger(type). Types: 'light'|'medium'|'heavy'|'success'|'warning'|'error'|'selection'|'impact'.\n\nWEB FALLBACK: Uses navigator.vibrate() \u2014 capability detected at module level so isSupported initializes correctly on first render (avoids first-call false bug). On desktop/browsers without vibrate: isSupported=false. setWebFallback(false) disables; when disabled, trigger() is a no-op.",
|
|
614
621
|
"layer": "Runtime",
|
|
615
622
|
"source": "static",
|
|
616
|
-
"tags": ["native-hooks", "useHapticFeedback", "return-shape"]
|
|
623
|
+
"tags": ["native-hooks", "useHapticFeedback", "return-shape", "web-fallback"]
|
|
617
624
|
},
|
|
618
625
|
{
|
|
619
626
|
"section": "native_hooks",
|
|
620
|
-
"title": "useNotification
|
|
621
|
-
"content": "useNotification() returns: { data, loading, progress, error, execute (=scheduleLocal), clear, clearError, permissionStatus, pushToken, badges, lastNotification, subscribedTopics, scheduleLocal(
|
|
627
|
+
"title": "useNotification \u2014 return shape",
|
|
628
|
+
"content": "useNotification() returns: { data, loading, progress, error, execute (=scheduleLocal), clear, clearError, isNative, isWeb, permissionStatus, pushToken, badges, lastNotification, subscribedTopics, scheduleLocal(config), schedule (alias for scheduleLocal), cancelLocal(id), registerForPush(), updateBadge(count), subscribeToTopic(topic), unsubscribeFromTopic(topic), getSubscribedTopics(), requestPermission() }. requestPermission() triggers native permission prompt (wraps requestNotificationPermission(), errors routed through base.handleNativeError). schedule is an alias for scheduleLocal. Requires WEBVIEW_CONFIG.notifications.enabled=true + Firebase files.",
|
|
622
629
|
"layer": "Runtime",
|
|
623
630
|
"source": "static",
|
|
624
631
|
"tags": ["native-hooks", "useNotification", "return-shape"]
|
|
625
632
|
},
|
|
626
633
|
{
|
|
627
634
|
"section": "native_hooks",
|
|
628
|
-
"title": "useNotificationPermission
|
|
635
|
+
"title": "useNotificationPermission \u2014 return shape",
|
|
629
636
|
"content": "useNotificationPermission() returns: { permission: string|null, isLoading: boolean }. permission values: 'granted' | 'denied' | 'not_determined'. Automatically requests permission on mount. Use before useNotification push registration.",
|
|
630
637
|
"layer": "Runtime",
|
|
631
638
|
"source": "static",
|
|
@@ -633,24 +640,24 @@
|
|
|
633
640
|
},
|
|
634
641
|
{
|
|
635
642
|
"section": "native_hooks",
|
|
636
|
-
"title": "useNetworkStatus
|
|
637
|
-
"content": "useNetworkStatus() returns: { online: boolean, type: string|null, error: string|null }. type: 'wifi'|'cellular'|null. On
|
|
643
|
+
"title": "useNetworkStatus \u2014 return shape",
|
|
644
|
+
"content": "useNetworkStatus() returns: { online: boolean, type: string|null, error: string|null, isNative: boolean, isWeb: boolean, webFallbackActive, webFallbackDisabled, setWebFallback }. type: 'wifi'|'cellular'|null. isNative=true when running in Android/iOS WebView. isWeb=!isNative. On native: live updates via WebBridge ON_NETWORK_STATUS_CHANGED.\n\nWEB FALLBACK: Initializes from navigator.onLine (defaults to true when navigator.onLine is not a boolean, e.g. some dev/Electron environments). Listens to window 'online'/'offline' events for live updates. type is always null on web. webFallback prop syncs dynamically after mount \u2014 setWebFallback(false) removes event listeners.\n\nManages webFallback state locally (thin-state hook, not via useBaseHook).",
|
|
638
645
|
"layer": "Runtime",
|
|
639
646
|
"source": "static",
|
|
640
|
-
"tags": ["native-hooks", "useNetworkStatus", "return-shape"]
|
|
647
|
+
"tags": ["native-hooks", "useNetworkStatus", "return-shape", "web-fallback"]
|
|
641
648
|
},
|
|
642
649
|
{
|
|
643
650
|
"section": "native_hooks",
|
|
644
|
-
"title": "useSafeArea
|
|
645
|
-
"content": "useSafeArea() returns: { top: number, right: number, bottom: number, left: number }
|
|
651
|
+
"title": "useSafeArea \u2014 return shape",
|
|
652
|
+
"content": "useSafeArea() returns: { top: number, right: number, bottom: number, left: number, isNative: boolean, isWeb: boolean, webFallbackActive, webFallbackDisabled, setWebFallback } \u2014 inset values in pixels. isNative=true when running in Android/iOS WebView. isWeb=!isNative. On native: live updates via WebBridge ON_SAFE_AREA_INSETS_UPDATED. No isNative guard needed \u2014 safe to use on both platforms.\n\nWEB FALLBACK: Reads CSS env() variables \u2014 env(safe-area-inset-top/right/bottom/left) \u2014 parsed via a hidden DOM element. Falls back to 0 if env() vars are not set (desktop browsers). Manages webFallback state locally (thin-state hook).",
|
|
646
653
|
"layer": "Runtime",
|
|
647
654
|
"source": "static",
|
|
648
|
-
"tags": ["native-hooks", "useSafeArea", "return-shape"]
|
|
655
|
+
"tags": ["native-hooks", "useSafeArea", "return-shape", "web-fallback"]
|
|
649
656
|
},
|
|
650
657
|
{
|
|
651
658
|
"section": "native_hooks",
|
|
652
|
-
"title": "Platform Detection
|
|
653
|
-
"content": "Three ways to detect native context. (1) Hook-level (preferred in React components): every hook exposes isNative boolean
|
|
659
|
+
"title": "Platform Detection \u2014 isNative vs window.NativeBridge",
|
|
660
|
+
"content": "Three ways to detect native context. (1) Hook-level (preferred in React components): every hook exposes isNative boolean \u2014 e.g. const { isNative, execute } = useFilePicker(). (2) From WebBridge.init() return value: const result = WebBridge.init(); const isNative = result.platform !== 'web'. result.platform is 'android' | 'ios' | 'web' \u2014 set by NativeBridge environment detection. Do NOT invent result.isNative \u2014 that property does not exist on the init return. (3) Raw global check (outside hooks and WebBridge): const isAndroid = !!window.NativeBridge; const isIOS = !!window.webkit?.messageHandlers?.NativeBridge; const isNative = isAndroid || isIOS. Do NOT use navigator.userAgent \u2014 unreliable inside WebView.",
|
|
654
661
|
"layer": "Runtime",
|
|
655
662
|
"source": "static",
|
|
656
663
|
"tags": ["native-hooks", "platform-detection", "NativeBridge", "isNative"]
|
|
@@ -671,15 +678,15 @@
|
|
|
671
678
|
},
|
|
672
679
|
{
|
|
673
680
|
"section": "config_structure",
|
|
674
|
-
"title": "moduleAliases
|
|
675
|
-
"content": "package.json must have a _moduleAliases key (used by module-alias package) with these 6 required entries (enforced by validator.js
|
|
681
|
+
"title": "moduleAliases \u2014 Required Path Aliases in package.json",
|
|
682
|
+
"content": "package.json must have a _moduleAliases key (used by module-alias package) with these 6 required entries (enforced by validator.js \u2014 missing any = server crash): @api -> api/ (WARNING: common bug \u2014 @api accidentally set to src/static/css in legacy repos, causes all API imports to break silently), @containers -> src/js/containers, @server -> server, @config -> config, @css -> src/static/css, @routes -> src/js/routes/. Additional aliases (@store, @utils, @components, etc.) are optional but recommended. Dollar-prefix aliases (, , , , ) are server-side only \u2014 do not import in client code. Restriction: alias names containing catalyst are reserved. Migration check: always verify @api points to the actual API module, not a static asset path.",
|
|
676
683
|
"layer": "Config",
|
|
677
684
|
"source": "extracted:src/scripts/validator.js",
|
|
678
685
|
"tags": ["module-aliases", "package-json", "required", "config"]
|
|
679
686
|
},
|
|
680
687
|
{
|
|
681
688
|
"section": "webview_config",
|
|
682
|
-
"title": "appInfo Key
|
|
689
|
+
"title": "appInfo Key \u2014 Build Identifier String",
|
|
683
690
|
"content": "WEBVIEW_CONFIG.appInfo is a REQUIRED string (despite being named optional in older docs). Missing appInfo = iOS build breaks at compile time. Format convention: {platform}-{date}-v{version}, e.g. android-5Feb2026-v2.1.0. Place at WEBVIEW_CONFIG top level (not inside android/ios blocks). Both android and ios builds read this field.",
|
|
684
691
|
"layer": "Config",
|
|
685
692
|
"source": "extracted:config/config.json+changelog",
|
|
@@ -687,12 +694,25 @@
|
|
|
687
694
|
},
|
|
688
695
|
{
|
|
689
696
|
"section": "build_system",
|
|
690
|
-
"title": "Offline Fallback
|
|
691
|
-
"content": "Added in canary.19. catalyst-core auto-packages public/offline.html into both Android and iOS native bundles. When the device loses connectivity inside the WebView, the native shell shows offline.html automatically, with a retry mechanism when connectivity is restored. To enable: create public/offline.html at project root. Minimum content: <!DOCTYPE html><html><body><h1>No internet connection</h1><p>Please check your network and try again.</p></body></html>. Missing the file: native build proceeds but shows a blank screen on connectivity loss (no error). File is served from the native bundle
|
|
697
|
+
"title": "Offline Fallback \u2014 public/offline.html",
|
|
698
|
+
"content": "Added in canary.19. catalyst-core auto-packages public/offline.html into both Android and iOS native bundles. When the device loses connectivity inside the WebView, the native shell shows offline.html automatically, with a retry mechanism when connectivity is restored. To enable: create public/offline.html at project root. Minimum content: <!DOCTYPE html><html><body><h1>No internet connection</h1><p>Please check your network and try again.</p></body></html>. Missing the file: native build proceeds but shows a blank screen on connectivity loss (no error). File is served from the native bundle \u2014 not from the web server \u2014 so it works fully offline.",
|
|
692
699
|
"layer": "Native",
|
|
693
700
|
"source": "extracted:changelog",
|
|
694
701
|
"tags": ["offline", "fallback", "native", "canary.19", "public"]
|
|
695
702
|
},
|
|
703
|
+
{
|
|
704
|
+
"section": "native_offline",
|
|
705
|
+
"title": "Route Offline Snapshots - Online Recovery Behavior",
|
|
706
|
+
"content": "Route offline snapshots are network-first while online. Cached route HTML is served only when eligible route navigation is offline or the navigation fetch fails. When connectivity returns, retry, reload, or a new navigation uses the network again and can refresh the snapshot. If the bundled offline fallback page is already visible, Catalyst does not auto-reload it on reconnect; the user must retry/reload/navigate. Static assets may still come from cache while fresh or revalidating. API responses and non-GET requests are not cached by route offline snapshots.",
|
|
707
|
+
"layer": "Runtime",
|
|
708
|
+
"source": "static",
|
|
709
|
+
"tags": ["offline", "route-snapshots", "network-first", "native", "service-worker"],
|
|
710
|
+
"github_files": [
|
|
711
|
+
"packages/catalyst-core/src/offline/catalyst-sw.js",
|
|
712
|
+
"packages/catalyst-core/src/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt",
|
|
713
|
+
"packages/catalyst-core/src/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift"
|
|
714
|
+
]
|
|
715
|
+
},
|
|
696
716
|
{
|
|
697
717
|
"section": "framework_identity",
|
|
698
718
|
"title": "Mono-Repository Support (canary.2)",
|
|
@@ -703,8 +723,8 @@
|
|
|
703
723
|
},
|
|
704
724
|
{
|
|
705
725
|
"section": "native_hooks",
|
|
706
|
-
"title": "WebBridge imperative methods
|
|
707
|
-
"content": "WebBridge.init() returns { bridge, platform, getDeviceInfo }
|
|
726
|
+
"title": "WebBridge imperative methods \u2014 source reference",
|
|
727
|
+
"content": "WebBridge.init() returns { bridge, platform, getDeviceInfo } \u2014 not a WebBridge instance directly. bridge = the WebBridge instance (also stored as window.WebBridge). platform = 'android' | 'ios' | 'web'. getDeviceInfo = bound method from bridge, callable directly from the return value. Two equivalent ways to call getDeviceInfo: (a) const result = WebBridge.init(); result.getDeviceInfo().then(...) (b) WebBridge.init(); window.WebBridge.getDeviceInfo().then(...) \u2014 window.WebBridge is the bridge instance set during init. Other imperative methods (openCamera, requestHapticFeedback, register, unregister, callback) are on the bridge instance only \u2014 access via window.WebBridge.openCamera() or result.bridge.openCamera(). Full source: node_modules/catalyst-core/src/native/bridge/WebBridge.js",
|
|
708
728
|
"layer": "Runtime",
|
|
709
729
|
"source": "extracted:WebBridge.js",
|
|
710
730
|
"tags": [
|
|
@@ -719,10 +739,10 @@
|
|
|
719
739
|
},
|
|
720
740
|
{
|
|
721
741
|
"section": "native_hooks",
|
|
722
|
-
"title": "Native hooks source
|
|
723
|
-
"content": "
|
|
742
|
+
"title": "Native hooks source \u2014 hooks/ directory",
|
|
743
|
+
"content": "hooks.js (node_modules/catalyst-core/src/native/bridge/hooks.js) is now a 2-line re-export shim \u2014 all hook implementations live in the hooks/ directory:\n\n- src/native/bridge/hooks/index.js \u2014 re-exports all hooks (public API)\n- src/native/bridge/hooks/utils.js \u2014 shared helpers (isWeb detection, etc.)\n- src/native/bridge/hooks/useCamera.js\n- src/native/bridge/hooks/useFilePicker.js\n- src/native/bridge/hooks/useHapticFeedback.js\n- src/native/bridge/hooks/useNotification.js\n- src/native/bridge/hooks/useVideoStream.js\n- src/native/bridge/hooks/useNetworkStatus.js\n- src/native/bridge/hooks/useDeviceInfo.js\n- src/native/bridge/hooks/useDataProtection.js\n- src/native/bridge/hooks/useSafeArea.js\n- src/native/bridge/hooks/useGoogleSignIn.js\n- src/native/bridge/hooks/useIntent.js\n\nAll existing import paths (from catalyst-core/hooks or catalyst-core/src/native/bridge/hooks.js) continue to work unchanged via the shim. useBaseHook lives at src/native/bridge/useBaseHook.js \u2014 the base for all standardized hooks with web fallback support.",
|
|
724
744
|
"layer": "Runtime",
|
|
725
|
-
"source": "extracted:hooks.js",
|
|
745
|
+
"source": "extracted:hooks/index.js",
|
|
726
746
|
"tags": [
|
|
727
747
|
"useCamera",
|
|
728
748
|
"useFilePicker",
|
|
@@ -732,14 +752,19 @@
|
|
|
732
752
|
"useNotification",
|
|
733
753
|
"useNetworkStatus",
|
|
734
754
|
"useSafeArea",
|
|
755
|
+
"useVideoStream",
|
|
756
|
+
"useDeviceInfo",
|
|
757
|
+
"useDataProtection",
|
|
735
758
|
"native-hooks",
|
|
736
759
|
"source-reference",
|
|
737
|
-
"
|
|
760
|
+
"hooks-directory",
|
|
761
|
+
"node_modules/catalyst-core/src/native/bridge/hooks.js",
|
|
762
|
+
"node_modules/catalyst-core/src/native/bridge/hooks/"
|
|
738
763
|
]
|
|
739
764
|
},
|
|
740
765
|
{
|
|
741
766
|
"section": "native_hooks",
|
|
742
|
-
"title": "NativeBridge utility
|
|
767
|
+
"title": "NativeBridge utility \u2014 source reference",
|
|
743
768
|
"content": "Low-level bridge plumbing lives in node_modules/catalyst-core/src/native/bridge/utils/NativeBridge.js. Handles message passing between JS and native layer. WebBridge.js wraps this. Read this file for understanding how postMessage, register/unregister, and native event handling work under the hood.",
|
|
744
769
|
"layer": "Runtime",
|
|
745
770
|
"source": "extracted:NativeBridge.js",
|
|
@@ -755,8 +780,8 @@
|
|
|
755
780
|
},
|
|
756
781
|
{
|
|
757
782
|
"section": "server_structure",
|
|
758
|
-
"title": "SSR renderer
|
|
759
|
-
"content": "rendertopipeable rendertopipeablestream: The full SSR render pipeline is in node_modules/catalyst-core/src/server/renderer/handler.js. This is the most important server file
|
|
783
|
+
"title": "SSR renderer \u2014 source reference",
|
|
784
|
+
"content": "rendertopipeable rendertopipeablestream: The full SSR render pipeline is in node_modules/catalyst-core/src/server/renderer/handler.js. This is the most important server file \u2014 read it for renderToPipeableStream usage, route matching, serverSideFunction calls, data fetching flow, and CompleteDocument assembly. Supporting files: src/server/renderer/document/ (Head.js, Body.js, index.js) for HTML document structure, src/server/renderer/extract.js for asset extraction.",
|
|
760
785
|
"layer": "Server",
|
|
761
786
|
"source": "extracted:handler.js",
|
|
762
787
|
"tags": [
|
|
@@ -774,8 +799,8 @@
|
|
|
774
799
|
},
|
|
775
800
|
{
|
|
776
801
|
"section": "server_structure",
|
|
777
|
-
"title": "renderToPipeableStream in catalyst
|
|
778
|
-
"content": "rendertopipeable rendertopipeablestream: Catalyst uses React 18 renderToPipeableStream (not renderToString) for main SSR. This enables streaming HTML: the shell (head + above-fold) is flushed immediately via onShellReady
|
|
802
|
+
"title": "renderToPipeableStream in catalyst \u2014 how and why",
|
|
803
|
+
"content": "rendertopipeable rendertopipeablestream: Catalyst uses React 18 renderToPipeableStream (not renderToString) for main SSR. This enables streaming HTML: the shell (head + above-fold) is flushed immediately via onShellReady \u2192 pipe(res), while deferred content streams as Suspense boundaries resolve. Flow: (1) onShellReady \u2014 sets content-type header, calls pipe(res) to start streaming. (2) onAllReady \u2014 writes firstFoldCss + firstFoldJS scripts, calls res.end(), cleans up globalThis. (3) onError \u2014 logs error, calls user-defined onRenderError callback, rejects promise. renderToString is still used internally for route matching / asset extraction (ChunkExtractor pass) but NOT for the final HTML response. Client hydration must use hydrateRoot (not createRoot) to attach to server-rendered HTML.",
|
|
779
804
|
"layer": "Server",
|
|
780
805
|
"source": "extracted:handler.js",
|
|
781
806
|
"tags": [
|
|
@@ -796,8 +821,8 @@
|
|
|
796
821
|
},
|
|
797
822
|
{
|
|
798
823
|
"section": "server_structure",
|
|
799
|
-
"title": "SSR pipeline
|
|
800
|
-
"content": "Full SSR request lifecycle: (1) Express receives request
|
|
824
|
+
"title": "SSR pipeline \u2014 full request flow",
|
|
825
|
+
"content": "Full SSR request lifecycle: (1) Express receives request \u2192 expressServer.js routes to renderer handler. (2) handler.js: matches routes via getMatchRoutes (uses react-router matchPath). (3) Calls serverSideFunction on matched route component for data fetching. (4) Assembles CompleteDocument (Head + Body with fetched data injected). (5) renderToPipeableStream streams HTML. onShellReady pipes shell immediately. onAllReady appends CSS/JS chunks. (6) Client receives streamed HTML, hydrateRoot attaches React. Key: serverSideFunction runs on server per-request \u2014 this is where route-level data fetching happens, result is passed as intialData (note: typo in source) to ServerRouter.",
|
|
801
826
|
"layer": "Server",
|
|
802
827
|
"source": "extracted:handler.js",
|
|
803
828
|
"tags": [
|
|
@@ -813,24 +838,24 @@
|
|
|
813
838
|
},
|
|
814
839
|
{
|
|
815
840
|
"section": "bridge_architecture",
|
|
816
|
-
"title": "getDeviceInfo
|
|
817
|
-
"content": "getDeviceInfo() is an imperative WebBridge method (not a React hook). Returns a Promise resolving to device metadata: model, manufacturer, platform, screenWidth, screenHeight, screenDensity, appInfo. \n\nWebBridge.init() return shape: { bridge: WebBridgeInstance, platform: 'android'|'ios'|'web', getDeviceInfo: boundFn }. bridge is also stored as window.WebBridge. \n\nCorrect usage pattern: const result = WebBridge.init(); const isNative = result.platform !== 'web'; result.getDeviceInfo().then(info => console.log(info)).catch(err => console.error(err)); \n\nDo NOT use result.isNative
|
|
841
|
+
"title": "getDeviceInfo \u2014 what it does and when to use it",
|
|
842
|
+
"content": "getDeviceInfo() is an imperative WebBridge method (not a React hook). Returns a Promise resolving to device metadata: model, manufacturer, platform, screenWidth, screenHeight, screenDensity, appInfo. \n\nWebBridge.init() return shape: { bridge: WebBridgeInstance, platform: 'android'|'ios'|'web', getDeviceInfo: boundFn }. bridge is also stored as window.WebBridge. \n\nCorrect usage pattern: const result = WebBridge.init(); const isNative = result.platform !== 'web'; result.getDeviceInfo().then(info => console.log(info)).catch(err => console.error(err)); \n\nDo NOT use result.isNative \u2014 that property does not exist. Do NOT check window.NativeBridge to gate the call \u2014 getDeviceInfo() handles web gracefully: on web it resolves immediately with browser info (navigator.userAgent, screen dimensions) instead of throwing. \n\nOn native: registers ON_DEVICE_INFO_SUCCESS / ON_DEVICE_INFO_ERROR callbacks, calls nativeBridge.device.getDeviceInfo(), resolves when native responds. \n\nAlternative call: window.WebBridge.getDeviceInfo() \u2014 works if init() was already called earlier (window.WebBridge is set by init).",
|
|
818
843
|
"layer": "Runtime",
|
|
819
844
|
"source": "extracted:WebBridge.js",
|
|
820
845
|
"tags": ["getDeviceInfo", "WebBridge", "device-info", "imperative", "native-only", "isNative"]
|
|
821
846
|
},
|
|
822
847
|
{
|
|
823
848
|
"section": "config_structure",
|
|
824
|
-
"title": "APPLICATION_UID
|
|
825
|
-
"content": "APPLICATION_UID is a UUID string in config/config.json that uniquely identifies the app to the catalyst runtime. Required field
|
|
849
|
+
"title": "APPLICATION_UID \u2014 What it is and where to get it",
|
|
850
|
+
"content": "APPLICATION_UID is a UUID string in config/config.json that uniquely identifies the app to the catalyst runtime. Required field \u2014 missing or empty = catalyst server startup error. Format: standard UUID v4 e.g. 9ba95d50-6396-42fb-956f-2d1ce98be8cb. How to generate: node -e \"const {randomUUID}=require(crypto); console.log(randomUUID())\" or use any UUID generator. Each app (mweb, dweb, pfizer etc.) must have a unique APPLICATION_UID \u2014 do not copy between projects. Also add APPLICATION_ID (integer, e.g. 1) alongside it.",
|
|
826
851
|
"layer": "Config",
|
|
827
852
|
"source": "static",
|
|
828
853
|
"tags": ["config", "required", "APPLICATION_UID", "startup"]
|
|
829
854
|
},
|
|
830
855
|
{
|
|
831
856
|
"section": "config_structure",
|
|
832
|
-
"title": "Migration Phase 0
|
|
833
|
-
"content": "Minimum steps to get a catalyst webview app running without any native bridge/hooks: (1) catalyst-core in package.json dependencies. (2) All native scripts in package.json scripts: buildApp:android, buildApp:ios, setupEmulator:android, setupEmulator:ios, devBuild, devServe. (3) config/config.json WEBVIEW_CONFIG with LOCAL_IP (not localhost), port, APPLICATION_UID, android block (appName, packageName, buildType, sdkPath, emulatorName), ios block (appName, appBundleId, simulatorName, buildType). (4) _moduleAliases correct in package.json
|
|
857
|
+
"title": "Migration Phase 0 \u2014 Bare Minimum to Boot App (No Bridge)",
|
|
858
|
+
"content": "Minimum steps to get a catalyst webview app running without any native bridge/hooks: (1) catalyst-core in package.json dependencies. (2) All native scripts in package.json scripts: buildApp:android, buildApp:ios, setupEmulator:android, setupEmulator:ios, devBuild, devServe. (3) config/config.json WEBVIEW_CONFIG with LOCAL_IP (not localhost), port, APPLICATION_UID, android block (appName, packageName, buildType, sdkPath, emulatorName), ios block (appName, appBundleId, simulatorName, buildType). (4) _moduleAliases correct in package.json \u2014 especially @api pointing to the right path. (5) accessControl.allowedUrls includes your API domains. App will load as a webview at this point \u2014 bridge features (camera, notifications, device info) will not work until Phase 2 (WebBridge init). This is the correct first milestone for any repo migration.",
|
|
834
859
|
"layer": "Config",
|
|
835
860
|
"source": "static",
|
|
836
861
|
"tags": ["migration", "bare-minimum", "config", "getting-started", "phase-0"]
|
|
@@ -838,14 +863,14 @@
|
|
|
838
863
|
{
|
|
839
864
|
"section": "bridge_architecture",
|
|
840
865
|
"title": "Migrating from custom webviewkit bridge to catalyst hooks",
|
|
841
|
-
"content": "Legacy apps (e.g. mweb) use a custom webviewkit bridge: window.AppCallbacks (Android), window.webkit.messageHandlers (iOS). Catalyst replaces this entirely. Migration phases: Phase 1 (bare minimum
|
|
866
|
+
"content": "Legacy apps (e.g. mweb) use a custom webviewkit bridge: window.AppCallbacks (Android), window.webkit.messageHandlers (iOS). Catalyst replaces this entirely. Migration phases: Phase 1 (bare minimum \u2014 app boots, no bridge needed). Phase 2 (WebBridge init): add WebBridge.init() to client entry file (src/client.js or src/app.js) \u2014 this registers the NativeBridge globally, replaces window.AppCallbacks pattern. Phase 3 (hook migration, file by file): replace webviewkit.isAndroid()/isIOS() with const { isNative } = useNetworkStatus() in React components, or window.NativeBridge presence check in imperative code. Replace webviewkit.call(methodName, info) with the relevant catalyst hook (useCamera, useIntent, useFilePicker etc.). Do NOT migrate all files at once \u2014 pick one component, validate it works natively, then proceed. webviewkit alias in package.json (webviewkit: src/js/webviewkit) can be kept during migration and removed once all usages replaced.",
|
|
842
867
|
"layer": "Bridge",
|
|
843
868
|
"source": "static",
|
|
844
869
|
"tags": ["migration", "webviewkit", "bridge", "WebBridge", "hooks", "isNative"]
|
|
845
870
|
},
|
|
846
871
|
{
|
|
847
872
|
"title": "App Icon File Placement (No Config Required)",
|
|
848
|
-
"content": "App icons are FILESYSTEM ONLY
|
|
873
|
+
"content": "App icons are FILESYSTEM ONLY \u2014 do NOT add any appIcons key to config.json or WEBVIEW_CONFIG. The build auto-discovers icons by hardcoded path and filename pattern. No configuration needed. \n\nANDROID \u2014 place files at public/android/appIcons/ with naming icon-{density}.{ext}: icon-mdpi.png, icon-hdpi.png, icon-xhdpi.png, icon-xxhdpi.png, icon-xxxhdpi.png. Extensions: png, jpg, jpeg. Build copies each to androidProject/app/src/main/res/mipmap-{density}/icon.{ext} and sets android:icon='@mipmap/icon' in AndroidManifest. If no custom icons found \u2192 falls back to bundled catalyst icon (xxxhdpi only). \n\nIOS \u2014 place files at public/ios/appIcons/ with naming icon-{WxH}-{scale}.{ext}: icon-20x20-2x.png, icon-20x20-3x.png, icon-29x29-2x.png, icon-29x29-3x.png, icon-40x40-2x.png, icon-40x40-3x.png, icon-60x60-2x.png, icon-60x60-3x.png, icon-1024x1024-1x.png. Partial sets are fine \u2014 only provided sizes get updated in Assets.xcassets/AppIcon.appiconset/Contents.json. \n\nSPLASH SCREEN ASSETS (also filesystem, no config path field): Android \u2192 public/android/splashscreen.{png|jpg|jpeg|gif|bmp|webp}. iOS \u2192 public/ios/splashscreen.{png|jpg|jpeg}. WEBVIEW_CONFIG.splashScreen controls appearance only (backgroundColor, duration, imageWidth, imageHeight, cornerRadius) \u2014 NOT the asset path. \n\nNOTIFICATION ICONS (both platforms, filesystem only): public/notification-icon.{ext} (small), public/notification-large.{ext} (large). Android \u2192 drawable/ic_notification. iOS \u2192 Assets.xcassets/NotificationIcon.imageset.",
|
|
849
874
|
"tags": [
|
|
850
875
|
"app-icon",
|
|
851
876
|
"icon",
|
|
@@ -867,7 +892,7 @@
|
|
|
867
892
|
{
|
|
868
893
|
"section": "observability",
|
|
869
894
|
"title": "Sentry Setup in Catalyst",
|
|
870
|
-
"content": "Catalyst provides built-in Sentry support via catalyst-core/sentry.\n\nSETUP: Create config/sentry.config.json in your project. Catalyst's loadEnvironmentVariables.js auto-reads it at startup and sets process.env.SENTRY_CONFIG. No manual env wiring needed.\n\nCONFIG SHAPE:\n{\n \"dsn\": \"https://<key>@sentry.example.com/<id>\",\n \"tracesSampleRate\": 1.0,\n \"clientOptions\": { \"environment\": \"production\", ... },\n \"serverOptions\": { \"environment\": \"production\", \"profilesSampleRate\": 1.0, ... }\n}\n\nUSAGE: Import and call init() yourself
|
|
895
|
+
"content": "Catalyst provides built-in Sentry support via catalyst-core/sentry.\n\nSETUP: Create config/sentry.config.json in your project. Catalyst's loadEnvironmentVariables.js auto-reads it at startup and sets process.env.SENTRY_CONFIG. No manual env wiring needed.\n\nCONFIG SHAPE:\n{\n \"dsn\": \"https://<key>@sentry.example.com/<id>\",\n \"tracesSampleRate\": 1.0,\n \"clientOptions\": { \"environment\": \"production\", ... },\n \"serverOptions\": { \"environment\": \"production\", \"profilesSampleRate\": 1.0, ... }\n}\n\nUSAGE: Import and call init() yourself \u2014 Catalyst does NOT auto-initialize Sentry.\nimport { init, captureException, captureMessage, addBreadcrumb } from 'catalyst-core/sentry'\n\n// Call early in client/index.js or server/index.js\ninit()\n\nSERVER vs CLIENT: init() auto-detects environment. Server-side loads @sentry/node, client-side loads @sentry/react. Same call works in both.\n\nPEER DEPS: Install @sentry/node and @sentry/react in your project \u2014 not bundled by catalyst-core.\n\nKILL SWITCH PATTERN (common in projects): Guard init() with an env flag like ENABLE_SENTRY=true so Sentry can be disabled without config changes.",
|
|
871
896
|
"layer": "Observability",
|
|
872
897
|
"source": "static",
|
|
873
898
|
"tags": [
|
|
@@ -885,7 +910,7 @@
|
|
|
885
910
|
{
|
|
886
911
|
"section": "observability",
|
|
887
912
|
"title": "OpenTelemetry Setup in Catalyst",
|
|
888
|
-
"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
|
|
913
|
+
"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.",
|
|
889
914
|
"layer": "Observability",
|
|
890
915
|
"source": "static",
|
|
891
916
|
"tags": [
|
|
@@ -905,7 +930,7 @@
|
|
|
905
930
|
{
|
|
906
931
|
"section": "observability",
|
|
907
932
|
"title": "Sentry ErrorBoundary",
|
|
908
|
-
"content": "catalyst-core/sentry exports an ErrorBoundary React component for catching render errors.\n\nUSAGE:\nimport { ErrorBoundary } from 'catalyst-core/sentry'\n\n<ErrorBoundary fallback={<MyErrorUI />}>\n <App />\n</ErrorBoundary>\n\nBEHAVIOR: On error, calls captureException with the error and componentStack. Renders fallback prop if provided, otherwise renders <h1>Something went wrong.</h1>.\n\nNo fallback prop needed
|
|
933
|
+
"content": "catalyst-core/sentry exports an ErrorBoundary React component for catching render errors.\n\nUSAGE:\nimport { ErrorBoundary } from 'catalyst-core/sentry'\n\n<ErrorBoundary fallback={<MyErrorUI />}>\n <App />\n</ErrorBoundary>\n\nBEHAVIOR: On error, calls captureException with the error and componentStack. Renders fallback prop if provided, otherwise renders <h1>Something went wrong.</h1>.\n\nNo fallback prop needed \u2014 it has a default. Wrapping the top-level app or critical subtrees is the typical usage.",
|
|
909
934
|
"layer": "Observability",
|
|
910
935
|
"source": "static",
|
|
911
936
|
"tags": ["sentry", "error-boundary", "ErrorBoundary", "react", "error-handling", "observability"]
|
|
@@ -913,7 +938,7 @@
|
|
|
913
938
|
{
|
|
914
939
|
"section": "build_system",
|
|
915
940
|
"title": "Webpack Customization via webpackConfig.js",
|
|
916
|
-
"content": "Every catalyst project has a webpackConfig.js at the project root. This is the ONLY supported way to customize webpack
|
|
941
|
+
"content": "Every catalyst project has a webpackConfig.js at the project root. This is the ONLY supported way to customize webpack \u2014 do not modify catalyst-core internals.\n\nEXPORTED KEYS:\n- developmentPlugins[]: merged into dev client webpack (webpack-dev-server run)\n- ssrPlugins[]: merged into both dev SSR watcher and production SSR build\n- clientPlugins[]: merged into production client build\n- splitChunksConfig: object \u2014 replaces the default commonVendor+utilityVendor split chunk strategy entirely\n- transpileModules[]: array of package names to whitelist from nodeExternals in SSR builds (use for ESM-only deps that need transpilation)\n\nDEFAULT (from template): all arrays empty, no splitChunksConfig \u2014 uses catalyst-core defaults.\n\nREAL USAGE PATTERNS:\nmweb clientPlugins: HTMLWebpackPlugin (offline.html), InjectManifest/Workbox (service worker), RoutesManifestPlugin (routes manifest from src/js/routes/index.js), UploadAssetsPlugin (S3 upload of assets + build output post-build).\nmweb splitChunksConfig: custom commonVendor test regex, minChunks:2 on utilityVendor, minRemainingSize:20KB.\npsp-pfizer clientPlugins: dual CompressionPlugin (gzip + brotli for JS/CSS/HTML/SVG). splitChunksConfig only applied when NODE_ENV=production (conditional spread pattern).\n\nCSS MODULE RULES (base.babel.js, not overridable):\n- .scss under src/static/css/base and node_modules \u2192 global (no css-loader modules)\n- all other .scss \u2192 CSS Modules, localIdentName from catalystConfig.cssModulesIdentifierDev/Prod\n- SCSS globals injected via sass-loader additionalData: $font_url, $url_for (from scssParams.js)\n\nBASE CONFIG (src/webpack/base.babel.js) handles: JS/TS via babel-loader, SCSS, CSS, images (url-loader+img-loader), SVG (@svgr/webpack), fonts (url-loader+file-loader), HTML (html-loader).\nDefinePlugin exposes CLIENT_ENV_VARIABLES from config/config.json plus BUILD_OUTPUT_PATH, PUBLIC_STATIC_ASSET_PATH, PUBLIC_STATIC_ASSET_URL, SENTRY_CONFIG to client bundle.",
|
|
917
942
|
"layer": "Build",
|
|
918
943
|
"source": "static",
|
|
919
944
|
"tags": [
|
|
@@ -937,7 +962,7 @@
|
|
|
937
962
|
{
|
|
938
963
|
"section": "build_system",
|
|
939
964
|
"title": "React Compiler (opt-in via webpackConfig.js)",
|
|
940
|
-
"content": "React Compiler is opt-in. Disabled by default
|
|
965
|
+
"content": "React Compiler is opt-in. Disabled by default \u2014 no babel-plugin-react-compiler is added unless explicitly enabled.\n\nENABLE: Add reactCompiler key to webpackConfig.js:\n reactCompiler: true // uses default options: { target: '19' }\n reactCompiler: { target: '19' } // custom options object\n\nHOW IT WORKS: babel.config.client.js and babel.config.ssr.js both read customWebpackConfig.reactCompiler. If truthy, babel-plugin-react-compiler is added as a babel plugin to BOTH client and SSR builds. If the value is an object, it's passed as plugin options directly; otherwise defaults to { target: '19' }.\n\nPEER DEPS (already in catalyst-core, no install needed in app):\n- babel-plugin-react-compiler@^19.0.0-beta\n- react-compiler-runtime@^19.0.0-beta\n- eslint-plugin-react-compiler@^19.0.0-beta\n\nCURRENT USAGE: Neither mweb nor psp-pfizer enable React Compiler \u2014 both ship without the reactCompiler key.",
|
|
941
966
|
"layer": "Build",
|
|
942
967
|
"source": "static",
|
|
943
968
|
"tags": [
|
|
@@ -954,7 +979,7 @@
|
|
|
954
979
|
{
|
|
955
980
|
"section": "build_system",
|
|
956
981
|
"title": "CLI Commands and Package Scripts",
|
|
957
|
-
"content": "The catalyst binary (bin/catalyst.js) is the sole entrypoint for all framework commands. Projects call it via npm scripts.\n\nWEB COMMANDS:\n- catalyst start -> dev mode: webpack-dev-server (HMR) + node server\n- catalyst build -> production webpack build (client + SSR)\n- catalyst serve -> start node server (after build)\n- catalyst devBuild -> production webpack build but assets served from local node server (IS_DEV_COMMAND=true)\n- catalyst devServe -> same as devBuild but with webpack watch mode\n\nNATIVE COMMANDS:\n- catalyst buildApp -> builds iOS + Android sequentially\n- catalyst buildApp:ios -> iOS app build only\n- catalyst buildApp:android -> Android app build only\n- catalyst setupEmulator -> sets up iOS + Android emulators sequentially\n- catalyst setupEmulator:ios -> iOS emulator setup only\n- catalyst setupEmulator:android -> Android emulator setup only\n\nINTERNALS:\n- Web commands resolve to dist/scripts/<command>.js (build.js, start.js, serve.js, devBuild.js, devServe.js)\n- Native commands resolve to dist/native/ (buildAppIos.js, buildAppAndroid.js, setupEmulatorIos.js, androidSetup.js)\n\nTYPICAL PROJECT package.json PATTERN:\n \"start\": \"catalyst start\"\n \"build\": \"export NODE_OPTIONS=\\\"--max-old-space-size=6020\\\" && catalyst build\"\n \"serve\": \"catalyst serve\"\n \"buildApp\": \"catalyst buildApp\"\n\nNOTE: serve:prod / serve:stag in project scripts are pm2-runtime commands
|
|
982
|
+
"content": "The catalyst binary (bin/catalyst.js) is the sole entrypoint for all framework commands. Projects call it via npm scripts.\n\nWEB COMMANDS:\n- catalyst start -> dev mode: webpack-dev-server (HMR) + node server\n- catalyst build -> production webpack build (client + SSR)\n- catalyst serve -> start node server (after build)\n- catalyst devBuild -> production webpack build but assets served from local node server (IS_DEV_COMMAND=true)\n- catalyst devServe -> same as devBuild but with webpack watch mode\n\nNATIVE COMMANDS:\n- catalyst buildApp -> builds iOS + Android sequentially\n- catalyst buildApp:ios -> iOS app build only\n- catalyst buildApp:android -> Android app build only\n- catalyst setupEmulator -> sets up iOS + Android emulators sequentially\n- catalyst setupEmulator:ios -> iOS emulator setup only\n- catalyst setupEmulator:android -> Android emulator setup only\n\nINTERNALS:\n- Web commands resolve to dist/scripts/<command>.js (build.js, start.js, serve.js, devBuild.js, devServe.js)\n- Native commands resolve to dist/native/ (buildAppIos.js, buildAppAndroid.js, setupEmulatorIos.js, androidSetup.js)\n\nTYPICAL PROJECT package.json PATTERN:\n \"start\": \"catalyst start\"\n \"build\": \"export NODE_OPTIONS=\\\"--max-old-space-size=6020\\\" && catalyst build\"\n \"serve\": \"catalyst serve\"\n \"buildApp\": \"catalyst buildApp\"\n\nNOTE: serve:prod / serve:stag in project scripts are pm2-runtime commands \u2014 not catalyst CLI commands. These are project-level and not part of the framework.",
|
|
958
983
|
"layer": "Build",
|
|
959
984
|
"source": "static",
|
|
960
985
|
"tags": [
|
|
@@ -976,7 +1001,7 @@
|
|
|
976
1001
|
{
|
|
977
1002
|
"section": "framework_identity",
|
|
978
1003
|
"title": "Project File Conventions and Structure",
|
|
979
|
-
"content": "Standard catalyst project layout:\n\nproject-root/\n
|
|
1004
|
+
"content": "Standard catalyst project layout:\n\nproject-root/\n\u251c\u2500\u2500 client/\n\u2502 \u251c\u2500\u2500 index.js <- webpack entry: hydrates React app (hydrateRoot + loadableReady)\n\u2502 \u2514\u2500\u2500 styles.js <- global CSS imports\n\u251c\u2500\u2500 server/\n\u2502 \u251c\u2500\u2500 index.js <- exports preServerInit() \u2014 runs before express starts (use for OTel, DB init, Sentry)\n\u2502 \u251c\u2500\u2500 server.js <- express server setup\n\u2502 \u2514\u2500\u2500 document.js <- HTML document shell (Head, Body)\n\u251c\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 js/\n\u2502 \u251c\u2500\u2500 pages/ <- route-level components, one folder per page\n\u2502 \u251c\u2500\u2500 containers/ <- data-connected components (mweb uses these as page-level too)\n\u2502 \u251c\u2500\u2500 components/ <- presentational/shared components\n\u2502 \u251c\u2500\u2500 layouts/ <- layout wrappers used as nested route parents\n\u2502 \u2514\u2500\u2500 routes/ <- index.js exports routes array; utils.js for route helpers\n\u2502 \u2514\u2500\u2500 static/\n\u2502 \u251c\u2500\u2500 css/base/ <- global SCSS \u2014 NOT CSS-moduled (applied globally)\n\u2502 \u2514\u2500\u2500 css/resources/ <- SCSS variables/mixins auto-injected into all CSS modules via sass-resources-loader\n\u251c\u2500\u2500 config/\n\u2502 \u2514\u2500\u2500 config.json <- app config: CLIENT_ENV_VARIABLES, WEBVIEW_CONFIG, ANALYZE_BUNDLE, etc.\n\u251c\u2500\u2500 api.js <- API layer (aliased as @api)\n\u251c\u2500\u2500 webpackConfig.js <- webpack customization hook\n\u2514\u2500\u2500 package.json <- _moduleAliases defines path aliases for webpack + node\n\nMODULE ALIASES (defined in package.json._moduleAliases, resolved by webpack and registerAliases.js):\n @api -> api.js\n @containers -> src/js/containers\n @server -> server\n @config -> config\n @css -> src/static/css\n @routes -> src/js/routes/\n\nKEY CONVENTIONS:\n- Pages use @loadable/component for code splitting. ssr:true enables SSR for that page; ssr:false serves fallback only.\n- client/index.js always uses hydrateRoot (not ReactDOM.render) wrapped in loadableReady.\n- server/index.js exports preServerInit() \u2014 the correct place for server-side init (OTel, Sentry, DB connections).\n- Routes in src/js/routes/index.js use nested structure: layout component as parent, pages as children.\n- Build output: build/public/ (client JS/CSS chunks), build/renderer/ (SSR handler.js + loadable-stats.json).\n- CSS co-located with components as .scss files (CSS Modules). Global styles go in src/static/css/base/.",
|
|
980
1005
|
"layer": "Framework",
|
|
981
1006
|
"source": "static",
|
|
982
1007
|
"tags": [
|
|
@@ -998,8 +1023,8 @@
|
|
|
998
1023
|
},
|
|
999
1024
|
{
|
|
1000
1025
|
"section": "native_hooks",
|
|
1001
|
-
"title": "Native Hooks
|
|
1002
|
-
"content": "
|
|
1026
|
+
"title": "Native Hooks \u2014 Full List and Standard Interface",
|
|
1027
|
+
"content": "All hooks live in src/native/bridge/hooks/ (one file per domain). hooks.js is a 2-line re-export shim for backwards compat.\n\nSTANDARD BASE INTERFACE (useBaseHook consumers):\n{ data, loading, progress, error, isWeb, isNative, execute, clear, clearError,\n webFallbackActive, webFallbackDisabled, setWebFallback }\n\nprogress shape: { state, percentage, message, phase, transport, bytesLoaded, bytesTotal }\n\nENVIRONMENT DETECTION (useBaseHook):\nisNative = window.WebBridge && (window.NativeBridge || window.webkit?.messageHandlers?.NativeBridge)\nisWeb = !isNative\n\nWEB FALLBACK SYSTEM:\n- Hooks that support a browser fallback declare hasWebFallback: true in useBaseHook options.\n- webFallbackActive defaults to true on web when a fallback exists (unless webFallback prop === false).\n- webFallback prop syncs dynamically after mount \u2014 all hooks (useBaseHook consumers and thin-state hooks) update webFallbackActive via a useEffect that watches the prop.\n- setWebFallback(false) disables fallback at runtime \u2014 React re-runs effects + cleanups (e.g. stops MediaStream, removes event listeners).\n- When disabled: webFallbackDisabled=true, all action methods are no-ops, data/error/loading are null/false.\n- Thin-state hooks (useNetworkStatus, useSafeArea, useDeviceInfo, useCameraPermission) manage webFallback locally and expose isNative, isWeb, webFallbackActive, webFallbackDisabled, setWebFallback \u2014 same surface as useBaseHook consumers.\n- Rules of Hooks: all hooks are always called unconditionally at top level \u2014 early returns and conditional branches only in return statements, never around hook calls.\n\nFULL HOOK LIST:\nWith web fallback:\n- useCamera \u2014 capture photo. Web: <input type=file capture=environment>.\n- useFilePicker \u2014 pick file. Web: <input type=file>.\n- useHapticFeedback \u2014 haptic/vibration. Web: navigator.vibrate.\n- useVideoStream \u2014 live camera stream + sendCommand (zoom/torch/fps). Web: getUserMedia (requires secure context) with track.applyConstraints. Stream auto-stopped on mode change or unmount.\n- useDataProtection \u2014 setScreenSecure + clearWebData. Web: overlay div on visibilitychange + localStorage/sessionStorage/cookie/IndexedDB clear.\n- useCameraPermission \u2014 camera permission state + request(). Web: navigator.permissions API (query) + getUserMedia side-effect for request().\n- useNetworkStatus \u2014 online/type. Web: navigator.onLine (defaults true if not boolean) + online/offline events (live updates).\n- useSafeArea \u2014 inset pixels. Web: CSS env() vars.\n- useDeviceInfo \u2014 device metadata. Web: navigator/screen APIs.\n\nNo web fallback (native-only or scoped out):\n- useNotification \u2014 push notifications. Web: scoped out (Service Worker required, unreliable in WebView).\n- useNotificationPermission \u2014 notification permission.\n- useGoogleSignIn \u2014 Google sign-in via native.\n- useIntent \u2014 open file with external app.",
|
|
1003
1028
|
"layer": "Bridge",
|
|
1004
1029
|
"source": "static",
|
|
1005
1030
|
"tags": [
|
|
@@ -1016,21 +1041,78 @@
|
|
|
1016
1041
|
"useCameraPermission",
|
|
1017
1042
|
"useNotificationPermission",
|
|
1018
1043
|
"useGoogleSignIn",
|
|
1019
|
-
"
|
|
1044
|
+
"useVideoStream",
|
|
1045
|
+
"useDeviceInfo",
|
|
1020
1046
|
"useBaseHook",
|
|
1047
|
+
"web-fallback",
|
|
1048
|
+
"webFallbackActive",
|
|
1049
|
+
"setWebFallback",
|
|
1021
1050
|
"native",
|
|
1022
1051
|
"bridge"
|
|
1023
1052
|
],
|
|
1024
1053
|
"github_files": [
|
|
1025
|
-
"src/native/bridge/hooks.js",
|
|
1026
|
-
"src/native/bridge/
|
|
1054
|
+
"src/native/bridge/hooks/index.js",
|
|
1055
|
+
"src/native/bridge/hooks/useCamera.js",
|
|
1056
|
+
"src/native/bridge/hooks/useVideoStream.js",
|
|
1027
1057
|
"src/native/bridge/useBaseHook.js"
|
|
1028
1058
|
]
|
|
1029
1059
|
},
|
|
1060
|
+
{
|
|
1061
|
+
"section": "native_hooks",
|
|
1062
|
+
"title": "useVideoStream \u2014 return shape",
|
|
1063
|
+
"content": "useVideoStream(options?) returns: { isStreaming: boolean, streamState: { zoom, minZoom, maxZoom, torchOn, fpsMin, fpsMax }, error, isNative, webFallbackActive, webFallbackDisabled, setWebFallback, mediaStream: MediaStream|null (web only), viewfinderRef, start, stop, sendCommand, flip, clearError }.\n\nstart(options?) / stop() / flip() \u2014 stream lifecycle. sendCommand(type, value) controls live stream: 'zoom' (number \u2265 1.0), 'torch' (boolean), 'fps' ({ min, max }).\n\nOn native: sendCommand routes to WebBridge native commands. streamState updates only on native confirmation events (ON_TORCH_CHANGED, ON_ZOOM_CHANGED). On web fallback: sendCommand maps to track.applyConstraints().\n\nWEB FALLBACK (getUserMedia):\n- Requires secure context (HTTPS or localhost) \u2014 hook returns error if window.isSecureContext is false.\n- Requires navigator.mediaDevices.getUserMedia \u2014 hook returns error if unavailable.\n- mediaStream is returned directly (attach to <video> srcObject).\n- All tracks stopped automatically when setWebFallback(false) is called or component unmounts.\n- start()/stop()/flip() are async on web.\n- Rules of Hooks: all hooks (useState, useRef, useCallback, useEffect) declared unconditionally at top level \u2014 branching only in return statements.",
|
|
1064
|
+
"layer": "Runtime",
|
|
1065
|
+
"source": "static",
|
|
1066
|
+
"tags": [
|
|
1067
|
+
"native-hooks",
|
|
1068
|
+
"useVideoStream",
|
|
1069
|
+
"return-shape",
|
|
1070
|
+
"web-fallback",
|
|
1071
|
+
"getUserMedia",
|
|
1072
|
+
"MediaStream",
|
|
1073
|
+
"sendCommand",
|
|
1074
|
+
"secure-context",
|
|
1075
|
+
"rules-of-hooks"
|
|
1076
|
+
]
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
"section": "native_hooks",
|
|
1080
|
+
"title": "useDeviceInfo \u2014 return shape",
|
|
1081
|
+
"content": "useDeviceInfo() returns: { data: { model, manufacturer, platform, osVersion, screenWidth, screenHeight, screenDensity, appVersion, appInfo }, loading, error, isWeb, isNative, webFallbackActive, webFallbackDisabled, setWebFallback }.\n\nThis is a NEW React hook (did not exist before \u2014 only WebBridge.getDeviceInfo() imperative method existed). Use useDeviceInfo() in React components instead of calling WebBridge.getDeviceInfo() imperatively.\n\nOn native: wraps WebBridge.getDeviceInfo() promise, resolves on ON_DEVICE_INFO_SUCCESS callback.\n\nWEB FALLBACK: Populates device info from browser APIs:\n- platform: navigator.platform or 'web'\n- model/manufacturer: derived from navigator.userAgent (best-effort)\n- screenWidth/Height: screen.width/height\n- screenDensity: window.devicePixelRatio\n- appVersion/appInfo: null on web (no native app context)\n\nManages webFallback state locally (thin-state hook, not via useBaseHook).",
|
|
1082
|
+
"layer": "Runtime",
|
|
1083
|
+
"source": "static",
|
|
1084
|
+
"tags": [
|
|
1085
|
+
"native-hooks",
|
|
1086
|
+
"useDeviceInfo",
|
|
1087
|
+
"return-shape",
|
|
1088
|
+
"web-fallback",
|
|
1089
|
+
"device-info",
|
|
1090
|
+
"navigator",
|
|
1091
|
+
"screen"
|
|
1092
|
+
]
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
"section": "native_hooks",
|
|
1096
|
+
"title": "useDataProtection \u2014 return shape",
|
|
1097
|
+
"content": "useDataProtection() returns: { data, loading, error, isWeb, isNative, execute, clear, clearError, setScreenSecure: (enabled: boolean) => void, clearWebData: () => void, isScreenSecure: boolean, webFallbackActive, webFallbackDisabled, setWebFallback }.\n\nsetScreenSecure(true/false) \u2014 enables/disables screen security (prevents screenshots/recents).\nclearWebData() \u2014 clears all web storage.\n\nOn native (Android): setScreenSecure sets FLAG_SECURE on WebView. clearWebData clears cookies, localStorage, WebView cache.\nOn native (iOS): setScreenSecure applies blur overlay. clearWebData uses WKWebsiteDataStore.\n\nWEB FALLBACK:\n- setScreenSecure(true): injects a fixed black overlay div (position:fixed, z-index:9999, 100vw/100vh) that shows/hides on document.visibilitychange \u2014 best-effort screenshot prevention (cannot block OS-level screenshots on web).\n- setScreenSecure(false): removes the overlay div and visibilitychange listener.\n- clearWebData(): clears localStorage, sessionStorage, all cookies (document.cookie), and all IndexedDB databases (indexedDB.databases() + deleteDatabase()).\n- setWebFallback(false): removes overlay + listener, returns inert no-op shape.",
|
|
1098
|
+
"layer": "Runtime",
|
|
1099
|
+
"source": "static",
|
|
1100
|
+
"tags": [
|
|
1101
|
+
"native-hooks",
|
|
1102
|
+
"useDataProtection",
|
|
1103
|
+
"return-shape",
|
|
1104
|
+
"web-fallback",
|
|
1105
|
+
"setScreenSecure",
|
|
1106
|
+
"clearWebData",
|
|
1107
|
+
"security",
|
|
1108
|
+
"overlay",
|
|
1109
|
+
"IndexedDB"
|
|
1110
|
+
]
|
|
1111
|
+
},
|
|
1030
1112
|
{
|
|
1031
1113
|
"section": "seo_metadata",
|
|
1032
|
-
"title": "Server-Side Meta Tags
|
|
1033
|
-
"content": "Each page component can define a static `setMetaData` function that returns an array of JSX meta elements. These are collected server-side and injected into the HTML <head> before the page is streamed to the client.\n\nHOW IT WORKS:\n1. Route components define `Component.setMetaData = (fetcherData) => [...]`\n2. handler.js calls `getMetaData(allMatches, fetcherData)` from @tata1mg/router
|
|
1114
|
+
"title": "Server-Side Meta Tags \u2014 setMetaData()",
|
|
1115
|
+
"content": "Each page component can define a static `setMetaData` function that returns an array of JSX meta elements. These are collected server-side and injected into the HTML <head> before the page is streamed to the client.\n\nHOW IT WORKS:\n1. Route components define `Component.setMetaData = (fetcherData) => [...]`\n2. handler.js calls `getMetaData(allMatches, fetcherData)` from @tata1mg/router \u2014 collects setMetaData from all matched route components, passes fetcherData so tags can be dynamic.\n3. The resulting `allTags` array is passed to `renderMarkUp()` \u2192 `<Head metaTags={allTags} />`.\n4. `Head` renders them inside `<head>` before pageCss and preloadJSLinks.\n\nEXAMPLE (static title):\n```js\nHome.setMetaData = () => {\n return [<title key=\"title\">Home</title>]\n}\n```\n\nEXAMPLE (dynamic OG tags using fetcherData):\n```js\nBreedDetails.setMetaData = ({ params, data }) => [\n <title key=\"title\">{params.breed} \u2014 Dog Breeds</title>,\n <meta key=\"og:title\" property=\"og:title\" content={params.breed} />,\n <meta key=\"og:description\" property=\"og:description\" content={data?.message} />,\n]\n```\n\nNOTES:\n- Always add `key` prop to each tag (React list reconciliation).\n- fetcherData shape matches what `serverFetcher` returned for that route.\n- Tags from parent + child routes are merged \u2014 order follows route nesting.\n- `isBot` flag is passed to Head; use for bot-specific meta if needed.",
|
|
1034
1116
|
"layer": "Component",
|
|
1035
1117
|
"source": "static",
|
|
1036
1118
|
"tags": [
|
|
@@ -1048,8 +1130,8 @@
|
|
|
1048
1130
|
},
|
|
1049
1131
|
{
|
|
1050
1132
|
"section": "seo_metadata",
|
|
1051
|
-
"title": "Client-Side Meta Updates
|
|
1052
|
-
"content": "On the client side, meta tags are kept in sync across navigations via `<MetaTag />` from @tata1mg/router. For global head additions (fonts, preconnect, etc.), use the custom `server/document.js`.\n\nMETATAG COMPONENT (client SPA navigation):\n- Placed inside `RouterDataProvider` in `preparedRoutes` (src/js/routes/utils.js).\n- Automatically reads `setMetaData` from the newly matched route component and updates the DOM <head> on every client-side navigation.\n- No manual setup needed beyond adding `<MetaTag />` once in the route tree.\n\nCUSTOM DOCUMENT (server/document.js):\n- Optional file at `server/document.js` in the project root.\n- Receives all Head/Body props and lets you wrap or extend the default HTML shell.\n- Use for: global fonts, preconnect links, custom <html> attributes.\n\nEXAMPLE (server/document.js):\n```js\nimport React from 'react'\nimport { Head, Body } from 'catalyst-core'\n\nexport default function Document(props) {\n return (\n <html lang=\"en\">\n <Head {...props}>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link href=\"https://fonts.googleapis.com/css2?family=Poppins\" rel=\"stylesheet\" />\n </Head>\n <Body {...props} />\n </html>\n )\n}\n```\n\nWhen `Head` receives `children`, it renders them first, then `metaTags`, then pageCss, then preloadJSLinks
|
|
1133
|
+
"title": "Client-Side Meta Updates \u2014 MetaTag + custom document",
|
|
1134
|
+
"content": "On the client side, meta tags are kept in sync across navigations via `<MetaTag />` from @tata1mg/router. For global head additions (fonts, preconnect, etc.), use the custom `server/document.js`.\n\nMETATAG COMPONENT (client SPA navigation):\n- Placed inside `RouterDataProvider` in `preparedRoutes` (src/js/routes/utils.js).\n- Automatically reads `setMetaData` from the newly matched route component and updates the DOM <head> on every client-side navigation.\n- No manual setup needed beyond adding `<MetaTag />` once in the route tree.\n\nCUSTOM DOCUMENT (server/document.js):\n- Optional file at `server/document.js` in the project root.\n- Receives all Head/Body props and lets you wrap or extend the default HTML shell.\n- Use for: global fonts, preconnect links, custom <html> attributes.\n\nEXAMPLE (server/document.js):\n```js\nimport React from 'react'\nimport { Head, Body } from 'catalyst-core'\n\nexport default function Document(props) {\n return (\n <html lang=\"en\">\n <Head {...props}>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link href=\"https://fonts.googleapis.com/css2?family=Poppins\" rel=\"stylesheet\" />\n </Head>\n <Body {...props} />\n </html>\n )\n}\n```\n\nWhen `Head` receives `children`, it renders them first, then `metaTags`, then pageCss, then preloadJSLinks \u2014 giving you full control over ordering.\n\nNOTES:\n- `MetaTag` is client-only; SSR uses `setMetaData` \u2192 `getMetaData` pipeline.\n- If no `server/document.js` is present, handler.js uses the default Head+Body layout.",
|
|
1053
1135
|
"layer": "Component",
|
|
1054
1136
|
"source": "static",
|
|
1055
1137
|
"tags": [
|
|
@@ -1064,5 +1146,29 @@
|
|
|
1064
1146
|
"fonts",
|
|
1065
1147
|
"preconnect"
|
|
1066
1148
|
]
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
"section": "config_structure",
|
|
1152
|
+
"title": "notifications Config \u2014 Required to Enable Android Permission",
|
|
1153
|
+
"content": "WEBVIEW_CONFIG.notifications.enabled is the gate for POST_NOTIFICATIONS in AndroidManifest.xml. When omitted or set to false, buildAppAndroid.js skips injecting POST_NOTIFICATIONS, WAKE_LOCK, RECEIVE_BOOT_COMPLETED permissions into the manifest. Effect: (1) Permission never appears in App Info \u2192 Notifications on device. (2) requestPermission() always returns DENIED \u2014 the OS never prompts the user. (3) Push notifications silently fail. Fix: set WEBVIEW_CONFIG.notifications.enabled = true in config/config.json and rebuild the app. Optional field: color (hex string, default \"#007AFF\") sets the notification accent color. Full shape: { enabled: true, color: \"#007AFF\" }. NOTE: local (scheduled) notifications may still deliver on some Android versions without this permission, but push and the permission prompt will not work.",
|
|
1154
|
+
"layer": "Config",
|
|
1155
|
+
"source": "extracted:src/native/buildAppAndroid.js",
|
|
1156
|
+
"tags": ["config", "notifications", "android", "permissions", "POST_NOTIFICATIONS"]
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
"section": "config_structure",
|
|
1160
|
+
"title": "Notification Sound File \u2014 Required Asset for Android Build",
|
|
1161
|
+
"content": "When WEBVIEW_CONFIG.notifications.enabled=true, the Android build injects a manifest meta-data entry referencing @raw/notification_sound_default. If no sound file is present in public/, the resource is never created and the build fails with: AAPT: error: resource raw/notification_sound_default not found. REQUIRED: place a sound file at public/notification-sound-default.mp3 (or .wav or .ogg) before running buildApp:android. The build copies it to res/raw/notification_sound_default.{ext} automatically. If you want the system default sound, still provide a silent audio file \u2014 Android will use it as the channel default. Similarly, public/notification-sound-urgent.mp3 is optional but follows the same pattern for the urgent channel.",
|
|
1162
|
+
"layer": "Config",
|
|
1163
|
+
"source": "extracted:src/native/buildAppAndroid.js",
|
|
1164
|
+
"tags": ["config", "notifications", "android", "assets", "build-error", "raw", "sound"]
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
"section": "config_structure",
|
|
1168
|
+
"title": "googleSignIn Config \u2014 Required to Enable Google Sign-In",
|
|
1169
|
+
"content": "WEBVIEW_CONFIG.googleSignIn.enabled=true is required to enable Google Sign-In on Android/iOS. Without it, any call to signIn() immediately fires ON_GOOGLE_SIGN_IN_ERROR with \"Google Sign-In is disabled in config\" \u2014 no prompt, no flow. Config shape: { googleSignIn: { enabled: true, clientId: \"YOUR_WEB_CLIENT_ID.apps.googleusercontent.com\" } }. clientId is the OAuth 2.0 Web Client ID from Google Cloud Console (not the Android client ID). It can also be passed per-call: signIn({ clientId: \"...\" }) \u2014 per-call value takes precedence over config. Both enabled and clientId are read from webview_config.properties at runtime (Gradle flattens WEBVIEW_CONFIG recursively: googleSignIn.enabled, googleSignIn.clientId). Also requires google-services.json in project root for the Android build.",
|
|
1170
|
+
"layer": "Config",
|
|
1171
|
+
"source": "extracted:src/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt",
|
|
1172
|
+
"tags": ["config", "googleSignIn", "android", "oauth", "sign-in"]
|
|
1067
1173
|
}
|
|
1068
1174
|
]
|