catalyst-core 0.1.0-beta.3 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/catalyst.js +8 -1
- package/changelog.md +6 -0
- package/dist/native/androidProject/app/build.gradle.kts +9 -0
- 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 +17 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +31 -3
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +148 -0
- 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/TransitionManager.kt +202 -0
- 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/utils/BridgeUtils.kt +42 -5
- 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/plugins/PluginBridgeTest.kt +139 -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 +8 -3
- package/dist/native/bridge/utils/NativeBridge.js +36 -1
- package/dist/native/buildAppAndroid.js +2 -2
- package/dist/native/buildAppIos.js +10 -17
- package/dist/native/internalPluginUtils.js +1 -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/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/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 +38 -7
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +6 -2
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +15 -4
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
- package/dist/native/plugin-bridge/PluginBridge.js +1 -0
- package/dist/native/pluginComposerAndroid.js +9 -0
- package/dist/native/pluginComposerIos.js +7 -0
- package/dist/otel.js +70 -2
- package/dist/scripts/plugins.js +1 -0
- package/dist/server/renderer/extract.js +3 -3
- package/dist/server/renderer/handler.js +15 -14
- package/dist/webpack/development.client.babel.js +5 -1
- package/dist/webpack/production.ssr.babel.js +5 -1
- package/mcp_v2/knowledge-base.json +1564 -1067
- package/package.json +1 -1
|
@@ -1,1068 +1,1565 @@
|
|
|
1
1
|
[
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
{
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
},
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
2
|
+
{
|
|
3
|
+
"section": "framework_identity",
|
|
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 \u2014 not React Native, not React Native Web.",
|
|
6
|
+
"layer": "Component",
|
|
7
|
+
"source": "static",
|
|
8
|
+
"tags": [
|
|
9
|
+
"core",
|
|
10
|
+
"architecture",
|
|
11
|
+
"universal"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"section": "framework_identity",
|
|
16
|
+
"title": "Not React Native",
|
|
17
|
+
"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.",
|
|
18
|
+
"layer": "Component",
|
|
19
|
+
"source": "static",
|
|
20
|
+
"tags": [
|
|
21
|
+
"architecture",
|
|
22
|
+
"constraints"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"section": "framework_identity",
|
|
27
|
+
"title": "Catalyst Competitive Advantage",
|
|
28
|
+
"content": "Best suited for form-heavy, content-rich apps. Wins on: UI consistency (identical HTML/CSS cross-platform), OTA web updates (web team ships mobile), custom security control (RootBeer/Frida/access control), no vendor lock-in, custom caching strategies.",
|
|
29
|
+
"layer": "Component",
|
|
30
|
+
"source": "static",
|
|
31
|
+
"tags": [
|
|
32
|
+
"positioning"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"section": "framework_identity",
|
|
37
|
+
"title": "Known Performance Gap: WebView Cold Start",
|
|
38
|
+
"content": "WebView cold start is the #1 performance gap vs React Native. Mitigation: SplashViewModel.swift preloading is the correct fix direction (not yet implemented in core).",
|
|
39
|
+
"layer": "Runtime",
|
|
40
|
+
"source": "static",
|
|
41
|
+
"tags": [
|
|
42
|
+
"performance",
|
|
43
|
+
"ios"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"section": "transport_architecture",
|
|
48
|
+
"title": "Tri-Transport System",
|
|
49
|
+
"content": "Catalyst uses three transports in priority order: 1) Native Bridge (primary, hardware/system access, low-latency), 2) Localhost Server (secondary, data-heavy operations, progress tracking, streaming), 3) Web Fallback (tertiary, browser APIs). Platform detection: const isWeb = !window.nativeBridge || typeof window.nativeBridge === 'undefined'",
|
|
50
|
+
"layer": "Runtime",
|
|
51
|
+
"source": "static",
|
|
52
|
+
"tags": [
|
|
53
|
+
"transport",
|
|
54
|
+
"architecture"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"section": "transport_architecture",
|
|
59
|
+
"title": "Operation Hook Pattern",
|
|
60
|
+
"content": "All async operations follow pattern: const { data, loading, error, progress } = useOperation(params). Allows consistent error handling, progress tracking, and loading states across all three transports.",
|
|
61
|
+
"layer": "Runtime",
|
|
62
|
+
"source": "static",
|
|
63
|
+
"tags": [
|
|
64
|
+
"hooks",
|
|
65
|
+
"api"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"section": "transport_architecture",
|
|
70
|
+
"title": "Operation Type Selection",
|
|
71
|
+
"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.",
|
|
72
|
+
"layer": "Runtime",
|
|
73
|
+
"source": "static",
|
|
74
|
+
"tags": [
|
|
75
|
+
"transport",
|
|
76
|
+
"operations"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"section": "transport_architecture",
|
|
81
|
+
"title": "Intent Size Limits",
|
|
82
|
+
"content": "2MB hard limit for direct Intent dispatch. Files 2MB-50MB use content provider (Android) or alternate server transport. Files >50MB use chunked streaming. Exceeding limits causes silent failure on Android.",
|
|
83
|
+
"layer": "Runtime",
|
|
84
|
+
"source": "static",
|
|
85
|
+
"tags": [
|
|
86
|
+
"transport",
|
|
87
|
+
"constraints"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"section": "transport_architecture",
|
|
92
|
+
"title": "Localhost Server Whitelist Requirement",
|
|
93
|
+
"content": "Localhost server transport requires accessControl.allowedUrls to include http://localhost:* pattern. If allowedUrls is empty, ALL URLs are blocked. If allowedUrls is defined, only whitelisted URLs allowed.",
|
|
94
|
+
"layer": "Config",
|
|
95
|
+
"source": "static",
|
|
96
|
+
"tags": [
|
|
97
|
+
"transport",
|
|
98
|
+
"security",
|
|
99
|
+
"config"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"section": "transport_architecture",
|
|
104
|
+
"title": "URL Whitelist Wildcard Support",
|
|
105
|
+
"content": "URL whitelist supports wildcard patterns: *.1mg.com* matches all 1mg subdomains. Whitelist checking is thread-safe. URL decoding bypass prevention prevents encoded URLs from bypassing whitelist checks.",
|
|
106
|
+
"layer": "Config",
|
|
107
|
+
"source": "static",
|
|
108
|
+
"tags": [
|
|
109
|
+
"security",
|
|
110
|
+
"url"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"section": "transport_architecture",
|
|
115
|
+
"title": "External Domain Handling",
|
|
116
|
+
"content": "isExternalDomain() function identifies URLs outside whitelist. External URLs open in system browser (Android: Intent.ACTION_VIEW, iOS: SFSafariViewController), not in WebView.",
|
|
117
|
+
"layer": "Runtime",
|
|
118
|
+
"source": "static",
|
|
119
|
+
"tags": [
|
|
120
|
+
"security",
|
|
121
|
+
"url"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"section": "bridge_architecture",
|
|
126
|
+
"title": "Android Bridge Flow",
|
|
127
|
+
"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.",
|
|
128
|
+
"layer": "Bridge",
|
|
129
|
+
"source": "static",
|
|
130
|
+
"tags": [
|
|
131
|
+
"android",
|
|
132
|
+
"bridge",
|
|
133
|
+
"ipc"
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"section": "bridge_architecture",
|
|
138
|
+
"title": "iOS Bridge Flow",
|
|
139
|
+
"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.",
|
|
140
|
+
"layer": "Bridge",
|
|
141
|
+
"source": "static",
|
|
142
|
+
"tags": [
|
|
143
|
+
"ios",
|
|
144
|
+
"bridge",
|
|
145
|
+
"ipc"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"section": "bridge_architecture",
|
|
150
|
+
"title": "Android Bridge Security",
|
|
151
|
+
"content": "@JavascriptInterface annotation + whitelist enforcement prevents injection. All data JSON-escaping required. NativeBridge.kt must use notifyWebJson + JSONObject, never string concatenation. Enforced at code review.",
|
|
152
|
+
"layer": "Bridge",
|
|
153
|
+
"source": "static",
|
|
154
|
+
"tags": [
|
|
155
|
+
"android",
|
|
156
|
+
"bridge",
|
|
157
|
+
"security"
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"section": "bridge_architecture",
|
|
162
|
+
"title": "iOS Bridge Security",
|
|
163
|
+
"content": "WKScriptMessageHandler + JSONSerialization (native JSON parsing). BridgeMessageValidator enforces message structure. Lazy handler initialization prevents zombie references. No string concatenation in response building.",
|
|
164
|
+
"layer": "Bridge",
|
|
165
|
+
"source": "static",
|
|
166
|
+
"tags": [
|
|
167
|
+
"ios",
|
|
168
|
+
"bridge",
|
|
169
|
+
"security"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"section": "bridge_architecture",
|
|
174
|
+
"title": "Bridge File Structure",
|
|
175
|
+
"content": "Key files: WebBridge.js (client-side bridge), NativeBridge.js (utility wrapper), NativeInterfaces.js (command/event constants), hooks.js (React hooks). Located in src/native/bridge/.",
|
|
176
|
+
"layer": "Bridge",
|
|
177
|
+
"source": "static",
|
|
178
|
+
"tags": [
|
|
179
|
+
"bridge",
|
|
180
|
+
"files"
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"section": "bridge_architecture",
|
|
185
|
+
"title": "isNative() Runtime Check Bug",
|
|
186
|
+
"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.",
|
|
187
|
+
"layer": "Bridge",
|
|
188
|
+
"source": "static",
|
|
189
|
+
"tags": [
|
|
190
|
+
"bridge",
|
|
191
|
+
"bug",
|
|
192
|
+
"ssr"
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"section": "bridge_architecture",
|
|
197
|
+
"title": "11 Native Commands Implemented",
|
|
198
|
+
"content": "Documented commands: OPEN_CAMERA, REQUEST_CAMERA_PERMISSION, PICK_FILE, OPEN_FILE_WITH_INTENT, GET_DEVICE_INFO, REQUEST_HAPTIC_FEEDBACK (+ 5 more). Full list in catalyst-docs.",
|
|
199
|
+
"layer": "Bridge",
|
|
200
|
+
"source": "static",
|
|
201
|
+
"tags": [
|
|
202
|
+
"bridge",
|
|
203
|
+
"commands"
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"section": "bridge_architecture",
|
|
208
|
+
"title": "14 Callback Events Implemented",
|
|
209
|
+
"content": "Camera: CAMERA_PERMISSION_STATUS. File: ON_FILE_PICKED, ON_FILE_PICK_ERROR, ON_FILE_DOWNLOADED, ON_DOWNLOAD_ERROR (+ 9 more events). Full list in catalyst-docs.",
|
|
210
|
+
"layer": "Bridge",
|
|
211
|
+
"source": "static",
|
|
212
|
+
"tags": [
|
|
213
|
+
"bridge",
|
|
214
|
+
"events"
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"section": "build_system",
|
|
219
|
+
"title": "WEBVIEW_CONFIG Location and Schema",
|
|
220
|
+
"content": "Location: config/config.json. Schema: { port, android: { buildType, sdkPath, emulatorName, cachePattern }, ios: { buildType, appBundleId, simulatorName, cachePattern } }. cachePattern is comma-separated globs: *.css,*.js,*.png,*.svg,*.jpg",
|
|
221
|
+
"layer": "Config",
|
|
222
|
+
"source": "static",
|
|
223
|
+
"tags": [
|
|
224
|
+
"config",
|
|
225
|
+
"build"
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"section": "build_system",
|
|
230
|
+
"title": "Android Build Pipeline",
|
|
231
|
+
"content": "1) Load WEBVIEW_CONFIG 2) Start dev server on port 3000 3) Validate SDK/ADB/emulator 4) Launch emulator 5) Copy web assets (dist) to native project 6) Gradle build 7) Install APK. Located in: src/native/androidProject/",
|
|
232
|
+
"layer": "Build",
|
|
233
|
+
"source": "static",
|
|
234
|
+
"tags": [
|
|
235
|
+
"android",
|
|
236
|
+
"build"
|
|
237
|
+
]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"section": "build_system",
|
|
241
|
+
"title": "iOS Build Pipeline",
|
|
242
|
+
"content": "1) Generate ConfigConstants.swift from WEBVIEW_CONFIG 2) Launch simulator 3) Clean Xcode project 4) Compile with Xcode 5) Install app + launch. Located in: src/native/iosnativeWebView/",
|
|
243
|
+
"layer": "Build",
|
|
244
|
+
"source": "static",
|
|
245
|
+
"tags": [
|
|
246
|
+
"ios",
|
|
247
|
+
"build"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"section": "build_system",
|
|
252
|
+
"title": "Build NPM Scripts",
|
|
253
|
+
"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.",
|
|
254
|
+
"layer": "Build",
|
|
255
|
+
"source": "static",
|
|
256
|
+
"tags": [
|
|
257
|
+
"build",
|
|
258
|
+
"scripts",
|
|
259
|
+
"package.json",
|
|
260
|
+
"native"
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"section": "build_system",
|
|
265
|
+
"title": "Cross-Platform Directory Copying",
|
|
266
|
+
"content": "Custom copyDirectory() JS utility (cross-platform recursive copy with exclusions) replaced rsync. Reason: rsync caused Windows issues, required manual CI install. New approach works on macOS, Windows, Linux without dependencies.",
|
|
267
|
+
"layer": "Build",
|
|
268
|
+
"source": "static",
|
|
269
|
+
"tags": [
|
|
270
|
+
"build",
|
|
271
|
+
"cross-platform"
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"section": "build_system",
|
|
276
|
+
"title": "Catalyst Dev Copy Workflow",
|
|
277
|
+
"content": "1) Edit source in catalyst-core/src/ 2) npm run prepare 3) Copy dist/native to node_modules/catalyst-core/ in test app 4) Copy package.json for exports map. Test app uses node_modules/catalyst-core/dist, not src directly.",
|
|
278
|
+
"layer": "Build",
|
|
279
|
+
"source": "static",
|
|
280
|
+
"tags": [
|
|
281
|
+
"build",
|
|
282
|
+
"workflow",
|
|
283
|
+
"development"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"section": "build_system",
|
|
288
|
+
"title": "ERR_PACKAGE_PATH_NOT_EXPORTED Fix",
|
|
289
|
+
"content": "Node 20 strict exports require ./package.json entry in exports map. Error occurs when test app imports from catalyst-core. Fix: copy catalyst-core/package.json to test app's node_modules/catalyst-core/package.json after each build.",
|
|
290
|
+
"layer": "Build",
|
|
291
|
+
"source": "static",
|
|
292
|
+
"tags": [
|
|
293
|
+
"build",
|
|
294
|
+
"nodejs",
|
|
295
|
+
"exports"
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"section": "security",
|
|
300
|
+
"title": "useDataProtection Hook",
|
|
301
|
+
"content": "Three primitives available: setScreenSecure (prevents screenshots/recents in Android, blur in iOS), getScreenSecure (internal only, read current state), clearWebData (clears all web storage). Called via bridge on app launch or user action.",
|
|
302
|
+
"layer": "Runtime",
|
|
303
|
+
"source": "static",
|
|
304
|
+
"tags": [
|
|
305
|
+
"security",
|
|
306
|
+
"hooks",
|
|
307
|
+
"dataprotection"
|
|
308
|
+
]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"section": "security",
|
|
312
|
+
"title": "FLAG_SECURE Android Behavior",
|
|
313
|
+
"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.",
|
|
314
|
+
"layer": "Runtime",
|
|
315
|
+
"source": "static",
|
|
316
|
+
"tags": [
|
|
317
|
+
"android",
|
|
318
|
+
"security",
|
|
319
|
+
"flag_secure"
|
|
320
|
+
]
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"section": "security",
|
|
324
|
+
"title": "allowBackup Configuration",
|
|
325
|
+
"content": "Location: WEBVIEW_CONFIG.android.security.allowBackup (true/false). Maps to android.security.allowBackup in AndroidManifest.xml via manifestPlaceholders[\"allowBackup\"] in defaultConfig. Controls whether app data backed up to Google cloud.",
|
|
326
|
+
"layer": "Config",
|
|
327
|
+
"source": "static",
|
|
328
|
+
"tags": [
|
|
329
|
+
"android",
|
|
330
|
+
"security",
|
|
331
|
+
"backup"
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"section": "security",
|
|
336
|
+
"title": "clearWebData Behavior",
|
|
337
|
+
"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.",
|
|
338
|
+
"layer": "Runtime",
|
|
339
|
+
"source": "static",
|
|
340
|
+
"tags": [
|
|
341
|
+
"security",
|
|
342
|
+
"cache",
|
|
343
|
+
"clearing"
|
|
344
|
+
]
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"section": "security",
|
|
348
|
+
"title": "iOS clearWebData Implementation",
|
|
349
|
+
"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.",
|
|
350
|
+
"layer": "Runtime",
|
|
351
|
+
"source": "static",
|
|
352
|
+
"tags": [
|
|
353
|
+
"ios",
|
|
354
|
+
"security",
|
|
355
|
+
"clearing"
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"section": "security",
|
|
360
|
+
"title": "iOS setScreenSecure JSON Parsing",
|
|
361
|
+
"content": "setScreenSecure params arrive as JSON string from JS bridge in iOS. Requires JSON-parse branch: if let str = params as? String, let jsonData = str.data(...). Android params already parsed as JSONObject.",
|
|
362
|
+
"layer": "Bridge",
|
|
363
|
+
"source": "static",
|
|
364
|
+
"tags": [
|
|
365
|
+
"ios",
|
|
366
|
+
"security",
|
|
367
|
+
"json"
|
|
368
|
+
]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
"section": "security",
|
|
372
|
+
"title": "Security Checks Run Release-Only",
|
|
373
|
+
"content": "RootBeer, Frida, emulator checks run only when !BuildConfig.DEBUG. Prevents false positives during development (emulator, rooted for testing, Frida server running).",
|
|
374
|
+
"layer": "Runtime",
|
|
375
|
+
"source": "static",
|
|
376
|
+
"tags": [
|
|
377
|
+
"android",
|
|
378
|
+
"security",
|
|
379
|
+
"checks"
|
|
380
|
+
]
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"section": "security",
|
|
384
|
+
"title": "Security Check Flow",
|
|
385
|
+
"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.",
|
|
386
|
+
"layer": "Runtime",
|
|
387
|
+
"source": "static",
|
|
388
|
+
"tags": [
|
|
389
|
+
"android",
|
|
390
|
+
"security",
|
|
391
|
+
"flow"
|
|
392
|
+
]
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"section": "security",
|
|
396
|
+
"title": "Security Compromise Detection",
|
|
397
|
+
"content": "isCompromised = true if ANY of: rooted OR emulator detected OR Frida detected. Single check triggers = compromise (strict, not majority vote). Recommendation: simple string 'BLOCK' or 'ALLOW' only.",
|
|
398
|
+
"layer": "Runtime",
|
|
399
|
+
"source": "static",
|
|
400
|
+
"tags": [
|
|
401
|
+
"android",
|
|
402
|
+
"security",
|
|
403
|
+
"detection"
|
|
404
|
+
]
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"section": "security",
|
|
408
|
+
"title": "RootBeer Version Requirement",
|
|
409
|
+
"content": "RootBeer 0.1.1 required for Android 15+ (handles 16KB page size alignment). Earlier versions fail on Android 15+.",
|
|
410
|
+
"layer": "Build",
|
|
411
|
+
"source": "static",
|
|
412
|
+
"tags": [
|
|
413
|
+
"android",
|
|
414
|
+
"security",
|
|
415
|
+
"dependencies"
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"section": "security",
|
|
420
|
+
"title": "Emulator Detection Threshold",
|
|
421
|
+
"content": "ANY single emulator check = emulator detection (not majority vote). Strict detection ensures no false negatives.",
|
|
422
|
+
"layer": "Runtime",
|
|
423
|
+
"source": "static",
|
|
424
|
+
"tags": [
|
|
425
|
+
"android",
|
|
426
|
+
"security",
|
|
427
|
+
"emulator"
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
"section": "security",
|
|
432
|
+
"title": "Frida Detection Port Scan",
|
|
433
|
+
"content": "Scans ports 27042, 27043 for Frida server. 4 second latency acceptable \u2014 runs on background coroutine while WebView already showing on clean devices.",
|
|
434
|
+
"layer": "Runtime",
|
|
435
|
+
"source": "static",
|
|
436
|
+
"tags": [
|
|
437
|
+
"android",
|
|
438
|
+
"security",
|
|
439
|
+
"frida"
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"section": "security",
|
|
444
|
+
"title": "Security Response Structure",
|
|
445
|
+
"content": "{ security: { isRooted: boolean, isEmulator: boolean, isFridaDetected: boolean, isCompromised: boolean, recommendation: 'BLOCK'|'ALLOW', timestamp: long } }",
|
|
446
|
+
"layer": "Runtime",
|
|
447
|
+
"source": "static",
|
|
448
|
+
"tags": [
|
|
449
|
+
"security",
|
|
450
|
+
"response"
|
|
451
|
+
]
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
"section": "security",
|
|
455
|
+
"title": "Security File Locations",
|
|
456
|
+
"content": "Security implementation files: src/native/androidProject/app/src/main/java/io/yourname/androidproject/security/. Includes SecurityCheckManager, RootBeer checks, Frida detection, SecurityAlertHandler.",
|
|
457
|
+
"layer": "Build",
|
|
458
|
+
"source": "static",
|
|
459
|
+
"tags": [
|
|
460
|
+
"android",
|
|
461
|
+
"security",
|
|
462
|
+
"files"
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"section": "security",
|
|
467
|
+
"title": "Play Integrity API Status",
|
|
468
|
+
"content": "Play Integrity integration deferred. All code commented with TODO markers. securityRoutes.js NOT committed. expressServer.js NOT committed. Local checks sufficient for now.",
|
|
469
|
+
"layer": "Runtime",
|
|
470
|
+
"source": "static",
|
|
471
|
+
"tags": [
|
|
472
|
+
"android",
|
|
473
|
+
"security",
|
|
474
|
+
"integrity"
|
|
475
|
+
]
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
"section": "caching",
|
|
479
|
+
"title": "Cache Pattern Bug and Fix",
|
|
480
|
+
"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.",
|
|
481
|
+
"layer": "Runtime",
|
|
482
|
+
"source": "static",
|
|
483
|
+
"tags": [
|
|
484
|
+
"caching",
|
|
485
|
+
"bug",
|
|
486
|
+
"fix"
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"section": "caching",
|
|
491
|
+
"title": "Custom Cache Implementation",
|
|
492
|
+
"content": "WebCacheManager.kt (Android) and CacheManager.swift (iOS) implement custom native disk+memory caches. Separate from WebView built-in cache (cookies, localStorage). Configured via WEBVIEW_CONFIG.*.cachePattern.",
|
|
493
|
+
"layer": "Runtime",
|
|
494
|
+
"source": "static",
|
|
495
|
+
"tags": [
|
|
496
|
+
"caching",
|
|
497
|
+
"android",
|
|
498
|
+
"ios"
|
|
499
|
+
]
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"section": "known_errors",
|
|
503
|
+
"title": "Silent Intent Failure on Size Exceeded",
|
|
504
|
+
"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.",
|
|
505
|
+
"layer": "Runtime",
|
|
506
|
+
"source": "static",
|
|
507
|
+
"tags": [
|
|
508
|
+
"android",
|
|
509
|
+
"error",
|
|
510
|
+
"intent"
|
|
511
|
+
]
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
"section": "known_errors",
|
|
515
|
+
"title": "isNative() Always Returns False (SSR)",
|
|
516
|
+
"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.",
|
|
517
|
+
"layer": "Bridge",
|
|
518
|
+
"source": "static",
|
|
519
|
+
"tags": [
|
|
520
|
+
"error",
|
|
521
|
+
"ssr",
|
|
522
|
+
"bridge",
|
|
523
|
+
"bug"
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"section": "known_errors",
|
|
528
|
+
"title": "Cache Hit Rate Near Zero",
|
|
529
|
+
"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.",
|
|
530
|
+
"layer": "Runtime",
|
|
531
|
+
"source": "static",
|
|
532
|
+
"tags": [
|
|
533
|
+
"error",
|
|
534
|
+
"caching",
|
|
535
|
+
"android",
|
|
536
|
+
"ios"
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
"section": "known_errors",
|
|
541
|
+
"title": "clearWebData Doesn't Clear Native Cache",
|
|
542
|
+
"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.",
|
|
543
|
+
"layer": "Runtime",
|
|
544
|
+
"source": "static",
|
|
545
|
+
"tags": [
|
|
546
|
+
"error",
|
|
547
|
+
"caching",
|
|
548
|
+
"security",
|
|
549
|
+
"clearing"
|
|
550
|
+
]
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"section": "known_errors",
|
|
554
|
+
"title": "Localhost Server Transport Not Working",
|
|
555
|
+
"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.",
|
|
556
|
+
"layer": "Config",
|
|
557
|
+
"source": "static",
|
|
558
|
+
"tags": [
|
|
559
|
+
"error",
|
|
560
|
+
"transport",
|
|
561
|
+
"server",
|
|
562
|
+
"config"
|
|
563
|
+
]
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"section": "known_errors",
|
|
567
|
+
"title": "ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
568
|
+
"content": "Symptom: import from catalyst-core fails with ERR_PACKAGE_PATH_NOT_EXPORTED in Node 20. Cause: Node 20 strict exports require ./package.json entry in exports map. Occurs when test app's node_modules/catalyst-core/package.json is outdated. Fix: copy catalyst-core/package.json to test app's node_modules/catalyst-core/ after each npm run prepare.",
|
|
569
|
+
"layer": "Build",
|
|
570
|
+
"source": "static",
|
|
571
|
+
"tags": [
|
|
572
|
+
"error",
|
|
573
|
+
"build",
|
|
574
|
+
"nodejs",
|
|
575
|
+
"exports"
|
|
576
|
+
]
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"section": "known_errors",
|
|
580
|
+
"title": "FLAG_SECURE Not Working on Emulator",
|
|
581
|
+
"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.",
|
|
582
|
+
"layer": "Runtime",
|
|
583
|
+
"source": "static",
|
|
584
|
+
"tags": [
|
|
585
|
+
"error",
|
|
586
|
+
"android",
|
|
587
|
+
"security",
|
|
588
|
+
"emulator"
|
|
589
|
+
]
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"section": "known_errors",
|
|
593
|
+
"title": "iOS clearWebData Appears Not Working",
|
|
594
|
+
"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.",
|
|
595
|
+
"layer": "Runtime",
|
|
596
|
+
"source": "static",
|
|
597
|
+
"tags": [
|
|
598
|
+
"error",
|
|
599
|
+
"ios",
|
|
600
|
+
"security",
|
|
601
|
+
"clearing"
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"section": "known_errors",
|
|
606
|
+
"title": "RootBeer Fails on Android 15+",
|
|
607
|
+
"content": "Symptom: app crashes or security checks fail on Android 15+ devices. Cause: older RootBeer versions don't support 16KB page size alignment required by Android 15. Fix: upgrade to RootBeer 0.1.1 minimum.",
|
|
608
|
+
"layer": "Build",
|
|
609
|
+
"source": "static",
|
|
610
|
+
"tags": [
|
|
611
|
+
"error",
|
|
612
|
+
"android",
|
|
613
|
+
"security",
|
|
614
|
+
"rootbeer"
|
|
615
|
+
]
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"section": "webview_config",
|
|
619
|
+
"title": "Real WEBVIEW_CONFIG Example",
|
|
620
|
+
"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.",
|
|
621
|
+
"layer": "Config",
|
|
622
|
+
"source": "extracted:config/config.json",
|
|
623
|
+
"tags": [
|
|
624
|
+
"config",
|
|
625
|
+
"example"
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
"section": "config_structure",
|
|
630
|
+
"title": "Full config/config.json Schema",
|
|
631
|
+
"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 }",
|
|
632
|
+
"layer": "Config",
|
|
633
|
+
"source": "extracted:src/scripts/validator.js",
|
|
634
|
+
"tags": [
|
|
635
|
+
"config",
|
|
636
|
+
"schema",
|
|
637
|
+
"required-fields"
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
"section": "config_structure",
|
|
642
|
+
"title": "WEBVIEW_CONFIG Required Fields",
|
|
643
|
+
"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)",
|
|
644
|
+
"layer": "Config",
|
|
645
|
+
"source": "static",
|
|
646
|
+
"tags": [
|
|
647
|
+
"config",
|
|
648
|
+
"WEBVIEW_CONFIG",
|
|
649
|
+
"android",
|
|
650
|
+
"ios",
|
|
651
|
+
"required",
|
|
652
|
+
"fields"
|
|
653
|
+
]
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"section": "config_structure",
|
|
657
|
+
"title": "splashScreen Config Structure",
|
|
658
|
+
"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).",
|
|
659
|
+
"layer": "Config",
|
|
660
|
+
"source": "static",
|
|
661
|
+
"tags": [
|
|
662
|
+
"splashscreen",
|
|
663
|
+
"splash",
|
|
664
|
+
"config",
|
|
665
|
+
"webview_config",
|
|
666
|
+
"android",
|
|
667
|
+
"ios",
|
|
668
|
+
"native",
|
|
669
|
+
"assets"
|
|
670
|
+
]
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"section": "config_structure",
|
|
674
|
+
"title": "accessControl Config",
|
|
675
|
+
"content": "WEBVIEW_CONFIG.accessControl: { enabled: boolean, allowedUrls: string[] }. When enabled=true and allowedUrls is non-empty, only whitelisted URLs load in WebView. When allowedUrls is empty, ALL URLs are blocked. Must include http://localhost:* for localhost server transport. Wildcard: *.domain.com* matches all subdomains.",
|
|
676
|
+
"layer": "Config",
|
|
677
|
+
"source": "static",
|
|
678
|
+
"tags": [
|
|
679
|
+
"config",
|
|
680
|
+
"security",
|
|
681
|
+
"access-control"
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"section": "routing",
|
|
686
|
+
"title": "Catalyst Routing Stack",
|
|
687
|
+
"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.",
|
|
688
|
+
"layer": "Runtime",
|
|
689
|
+
"source": "static",
|
|
690
|
+
"tags": [
|
|
691
|
+
"routing",
|
|
692
|
+
"react-router",
|
|
693
|
+
"required"
|
|
694
|
+
]
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
"section": "routing",
|
|
698
|
+
"title": "Route Definition Format",
|
|
699
|
+
"content": "Routes defined as array of objects: [{ path: '/', component: HomePage, exact: true }, { path: '/product/:id', component: ProductPage }]. Each component must export serverFetcher and/or clientFetcher as static properties for data loading. Route file location: src/js/routes/index.js.",
|
|
700
|
+
"layer": "Runtime",
|
|
701
|
+
"source": "static",
|
|
702
|
+
"tags": [
|
|
703
|
+
"routing",
|
|
704
|
+
"routes",
|
|
705
|
+
"format"
|
|
706
|
+
]
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
"section": "routing",
|
|
710
|
+
"title": "RouterDataProvider Setup",
|
|
711
|
+
"content": "RouterDataProvider from @tata1mg/router wraps the app in src/js/routes/utils.js. Provides route-level data context to all child components. useCurrentRouteData() reads from this context. Missing RouterDataProvider = all useCurrentRouteData() calls return undefined, no error thrown.",
|
|
712
|
+
"layer": "Runtime",
|
|
713
|
+
"source": "static",
|
|
714
|
+
"tags": [
|
|
715
|
+
"routing",
|
|
716
|
+
"RouterDataProvider",
|
|
717
|
+
"context"
|
|
718
|
+
]
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
"section": "routing",
|
|
722
|
+
"title": "App Shell with Outlet",
|
|
723
|
+
"content": "src/js/containers/App/index.js must render <Outlet /> from @tata1mg/router (not react-router-dom directly). Outlet renders the matched route component. Without Outlet, routing matches but nothing renders. App shell is also where global layout (nav, footer) lives.",
|
|
724
|
+
"layer": "Component",
|
|
725
|
+
"source": "static",
|
|
726
|
+
"tags": [
|
|
727
|
+
"routing",
|
|
728
|
+
"outlet",
|
|
729
|
+
"app-shell"
|
|
730
|
+
]
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
"section": "data_fetching",
|
|
734
|
+
"title": "serverFetcher Pattern",
|
|
735
|
+
"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.",
|
|
736
|
+
"layer": "Runtime",
|
|
737
|
+
"source": "static",
|
|
738
|
+
"tags": [
|
|
739
|
+
"data-fetching",
|
|
740
|
+
"ssr",
|
|
741
|
+
"server-fetcher"
|
|
742
|
+
]
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
"section": "data_fetching",
|
|
746
|
+
"title": "clientFetcher Pattern",
|
|
747
|
+
"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.",
|
|
748
|
+
"layer": "Runtime",
|
|
749
|
+
"source": "static",
|
|
750
|
+
"tags": [
|
|
751
|
+
"data-fetching",
|
|
752
|
+
"csr",
|
|
753
|
+
"client-fetcher"
|
|
754
|
+
]
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
"section": "data_fetching",
|
|
758
|
+
"title": "useCurrentRouteData Hook",
|
|
759
|
+
"content": "import { useCurrentRouteData } from '@tata1mg/router'. Returns data resolved by serverFetcher/clientFetcher for the current route. Replaces all useState + useEffect data loading patterns. Available only inside RouterDataProvider tree. Returns undefined if RouterDataProvider missing.",
|
|
760
|
+
"layer": "Runtime",
|
|
761
|
+
"source": "static",
|
|
762
|
+
"tags": [
|
|
763
|
+
"data-fetching",
|
|
764
|
+
"hooks",
|
|
765
|
+
"useCurrentRouteData"
|
|
766
|
+
]
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"section": "data_fetching",
|
|
770
|
+
"title": "Migration: useEffect \u2192 serverFetcher",
|
|
771
|
+
"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.",
|
|
772
|
+
"layer": "Runtime",
|
|
773
|
+
"source": "static",
|
|
774
|
+
"tags": [
|
|
775
|
+
"data-fetching",
|
|
776
|
+
"migration",
|
|
777
|
+
"useEffect"
|
|
778
|
+
]
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
"section": "server_structure",
|
|
782
|
+
"title": "Required Server Files",
|
|
783
|
+
"content": "Catalyst requires 3 specific files in server/: server/index.js (lifecycle hooks), server/server.js (Express middleware), server/document.js (HTML template). These are NOT optional. Missing any one = server startup fails. Any repo using custom Express, Next.js, CRA, or Vite must rebuild all 3 from scratch.",
|
|
784
|
+
"layer": "Build",
|
|
785
|
+
"source": "static",
|
|
786
|
+
"tags": [
|
|
787
|
+
"server",
|
|
788
|
+
"required-files",
|
|
789
|
+
"structure"
|
|
790
|
+
]
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"section": "server_structure",
|
|
794
|
+
"title": "server/index.js Lifecycle Hooks",
|
|
795
|
+
"content": "server/index.js exports lifecycle hooks: { preServerInit, onRouteMatch, onServerError }. preServerInit runs before Express starts (DB connections, config loading). onRouteMatch runs on each request (auth checks, redirects). onServerError handles uncaught errors. Minimum: export default {} (empty object) is valid.",
|
|
796
|
+
"layer": "Build",
|
|
797
|
+
"source": "static",
|
|
798
|
+
"tags": [
|
|
799
|
+
"server",
|
|
800
|
+
"lifecycle",
|
|
801
|
+
"hooks"
|
|
802
|
+
]
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"section": "server_structure",
|
|
806
|
+
"title": "server/server.js Middleware",
|
|
807
|
+
"content": "server/server.js must export addMiddlewares(app) function: export const addMiddlewares = (app) => { app.use(cors()); app.use(helmet()); }. Catalyst calls addMiddlewares(expressApp) before routes are registered. Use this for auth middleware, rate limiting, request logging, CORS, etc.",
|
|
808
|
+
"layer": "Build",
|
|
809
|
+
"source": "static",
|
|
810
|
+
"tags": [
|
|
811
|
+
"server",
|
|
812
|
+
"middleware",
|
|
813
|
+
"express"
|
|
814
|
+
]
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
"section": "server_structure",
|
|
818
|
+
"title": "server/document.js HTML Template",
|
|
819
|
+
"content": "server/document.js exports HTML shell for SSR responses: export default ({ html, head, scripts }) => `<!DOCTYPE html><html><head>${head}</head><body><div id='root'>${html}</div>${scripts}</body></html>`. Catalyst injects SSR HTML, CSS links, and script tags via the template params. Custom meta tags, fonts, and analytics go here.",
|
|
820
|
+
"layer": "Build",
|
|
821
|
+
"source": "static",
|
|
822
|
+
"tags": [
|
|
823
|
+
"server",
|
|
824
|
+
"ssr",
|
|
825
|
+
"html-template"
|
|
826
|
+
]
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
"section": "server_structure",
|
|
830
|
+
"title": "Client Entry Files",
|
|
831
|
+
"content": "client/index.js hydrates SSR markup: import { hydrate } from '@tata1mg/router'; hydrate(<App />, document.getElementById('root')). client/styles.js imports global CSS: import '../src/styles/global.css'. Both files required. Missing client/index.js = blank page on web (no hydration). Missing client/styles.js = styles not bundled.",
|
|
832
|
+
"layer": "Build",
|
|
833
|
+
"source": "static",
|
|
834
|
+
"tags": [
|
|
835
|
+
"client",
|
|
836
|
+
"hydration",
|
|
837
|
+
"entry-files"
|
|
838
|
+
]
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
"section": "native_hooks",
|
|
842
|
+
"title": "Native Hooks \u2014 Overview & Common Interface",
|
|
843
|
+
"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.",
|
|
844
|
+
"layer": "Runtime",
|
|
845
|
+
"source": "static",
|
|
846
|
+
"tags": [
|
|
847
|
+
"native-hooks",
|
|
848
|
+
"overview",
|
|
849
|
+
"base-interface",
|
|
850
|
+
"web-fallback",
|
|
851
|
+
"webFallbackActive",
|
|
852
|
+
"setWebFallback"
|
|
853
|
+
]
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"section": "native_hooks",
|
|
857
|
+
"title": "useCamera \u2014 return shape",
|
|
858
|
+
"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.",
|
|
859
|
+
"layer": "Runtime",
|
|
860
|
+
"source": "static",
|
|
861
|
+
"tags": [
|
|
862
|
+
"native-hooks",
|
|
863
|
+
"useCamera",
|
|
864
|
+
"return-shape",
|
|
865
|
+
"web-fallback"
|
|
866
|
+
]
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
"section": "native_hooks",
|
|
870
|
+
"title": "useCameraPermission \u2014 return shape",
|
|
871
|
+
"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.",
|
|
872
|
+
"layer": "Runtime",
|
|
873
|
+
"source": "static",
|
|
874
|
+
"tags": [
|
|
875
|
+
"native-hooks",
|
|
876
|
+
"useCameraPermission",
|
|
877
|
+
"return-shape",
|
|
878
|
+
"web-fallback"
|
|
879
|
+
]
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"section": "native_hooks",
|
|
883
|
+
"title": "useFilePicker \u2014 return shape",
|
|
884
|
+
"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.",
|
|
885
|
+
"layer": "Runtime",
|
|
886
|
+
"source": "static",
|
|
887
|
+
"tags": [
|
|
888
|
+
"native-hooks",
|
|
889
|
+
"useFilePicker",
|
|
890
|
+
"return-shape",
|
|
891
|
+
"web-fallback"
|
|
892
|
+
]
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
"section": "native_hooks",
|
|
896
|
+
"title": "useIntent \u2014 return shape",
|
|
897
|
+
"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.",
|
|
898
|
+
"layer": "Runtime",
|
|
899
|
+
"source": "static",
|
|
900
|
+
"tags": [
|
|
901
|
+
"native-hooks",
|
|
902
|
+
"useIntent",
|
|
903
|
+
"return-shape"
|
|
904
|
+
]
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
"section": "native_hooks",
|
|
908
|
+
"title": "useGoogleSignIn \u2014 return shape",
|
|
909
|
+
"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.",
|
|
910
|
+
"layer": "Runtime",
|
|
911
|
+
"source": "static",
|
|
912
|
+
"tags": [
|
|
913
|
+
"native-hooks",
|
|
914
|
+
"useGoogleSignIn",
|
|
915
|
+
"return-shape"
|
|
916
|
+
]
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
"section": "native_hooks",
|
|
920
|
+
"title": "useHapticFeedback \u2014 return shape",
|
|
921
|
+
"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.",
|
|
922
|
+
"layer": "Runtime",
|
|
923
|
+
"source": "static",
|
|
924
|
+
"tags": [
|
|
925
|
+
"native-hooks",
|
|
926
|
+
"useHapticFeedback",
|
|
927
|
+
"return-shape",
|
|
928
|
+
"web-fallback"
|
|
929
|
+
]
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
"section": "native_hooks",
|
|
933
|
+
"title": "useNotification \u2014 return shape",
|
|
934
|
+
"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.",
|
|
935
|
+
"layer": "Runtime",
|
|
936
|
+
"source": "static",
|
|
937
|
+
"tags": [
|
|
938
|
+
"native-hooks",
|
|
939
|
+
"useNotification",
|
|
940
|
+
"return-shape"
|
|
941
|
+
]
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"section": "native_hooks",
|
|
945
|
+
"title": "useNotificationPermission \u2014 return shape",
|
|
946
|
+
"content": "useNotificationPermission() returns: { permission: string|null, isLoading: boolean }. permission values: 'granted' | 'denied' | 'not_determined'. Automatically requests permission on mount. Use before useNotification push registration.",
|
|
947
|
+
"layer": "Runtime",
|
|
948
|
+
"source": "static",
|
|
949
|
+
"tags": [
|
|
950
|
+
"native-hooks",
|
|
951
|
+
"useNotificationPermission",
|
|
952
|
+
"return-shape"
|
|
953
|
+
]
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"section": "native_hooks",
|
|
957
|
+
"title": "useNetworkStatus \u2014 return shape",
|
|
958
|
+
"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).",
|
|
959
|
+
"layer": "Runtime",
|
|
960
|
+
"source": "static",
|
|
961
|
+
"tags": [
|
|
962
|
+
"native-hooks",
|
|
963
|
+
"useNetworkStatus",
|
|
964
|
+
"return-shape",
|
|
965
|
+
"web-fallback"
|
|
966
|
+
]
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
"section": "native_hooks",
|
|
970
|
+
"title": "useSafeArea \u2014 return shape",
|
|
971
|
+
"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).",
|
|
972
|
+
"layer": "Runtime",
|
|
973
|
+
"source": "static",
|
|
974
|
+
"tags": [
|
|
975
|
+
"native-hooks",
|
|
976
|
+
"useSafeArea",
|
|
977
|
+
"return-shape",
|
|
978
|
+
"web-fallback"
|
|
979
|
+
]
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
"section": "native_hooks",
|
|
983
|
+
"title": "Platform Detection \u2014 isNative vs window.NativeBridge",
|
|
984
|
+
"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.",
|
|
985
|
+
"layer": "Runtime",
|
|
986
|
+
"source": "static",
|
|
987
|
+
"tags": [
|
|
988
|
+
"native-hooks",
|
|
989
|
+
"platform-detection",
|
|
990
|
+
"NativeBridge",
|
|
991
|
+
"isNative"
|
|
992
|
+
]
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
"section": "native_hooks",
|
|
996
|
+
"title": "Imperative APIs (non-hook)",
|
|
997
|
+
"content": "Three promise-based imperative functions for use outside React components: requestCameraPermission() => Promise<string>, requestNotificationPermission() => Promise<string>, requestHapticFeedback(type) => Promise<string>. Both throw if window.WebBridge is not initialized.",
|
|
998
|
+
"layer": "Runtime",
|
|
999
|
+
"source": "static",
|
|
1000
|
+
"tags": [
|
|
1001
|
+
"native-hooks",
|
|
1002
|
+
"imperative",
|
|
1003
|
+
"requestCameraPermission",
|
|
1004
|
+
"requestNotificationPermission",
|
|
1005
|
+
"requestHapticFeedback"
|
|
1006
|
+
]
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
"section": "config_structure",
|
|
1010
|
+
"title": "moduleAliases \u2014 Required Path Aliases in package.json",
|
|
1011
|
+
"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.",
|
|
1012
|
+
"layer": "Config",
|
|
1013
|
+
"source": "extracted:src/scripts/validator.js",
|
|
1014
|
+
"tags": [
|
|
1015
|
+
"module-aliases",
|
|
1016
|
+
"package-json",
|
|
1017
|
+
"required",
|
|
1018
|
+
"config"
|
|
1019
|
+
]
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
"section": "webview_config",
|
|
1023
|
+
"title": "appInfo Key \u2014 Build Identifier String",
|
|
1024
|
+
"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.",
|
|
1025
|
+
"layer": "Config",
|
|
1026
|
+
"source": "extracted:config/config.json+changelog",
|
|
1027
|
+
"tags": [
|
|
1028
|
+
"config",
|
|
1029
|
+
"appInfo",
|
|
1030
|
+
"required",
|
|
1031
|
+
"ios",
|
|
1032
|
+
"build"
|
|
1033
|
+
]
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
"section": "build_system",
|
|
1037
|
+
"title": "Offline Fallback \u2014 public/offline.html",
|
|
1038
|
+
"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.",
|
|
1039
|
+
"layer": "Native",
|
|
1040
|
+
"source": "extracted:changelog",
|
|
1041
|
+
"tags": [
|
|
1042
|
+
"offline",
|
|
1043
|
+
"fallback",
|
|
1044
|
+
"native",
|
|
1045
|
+
"canary.19",
|
|
1046
|
+
"public"
|
|
1047
|
+
]
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"section": "framework_identity",
|
|
1051
|
+
"title": "Mono-Repository Support (canary.2)",
|
|
1052
|
+
"content": "Added in canary.2. catalyst-core supports mono-repo project structures where the catalyst app lives as a sub-package (e.g. packages/mweb/) rather than at the repo root. setup.js and MCP findCatalystRoot() both support this: they walk up from __dirname to find the nearest package.json with catalyst-core in dependencies, not assuming project root = cwd. For mono-repos: run 'node .catalyst/mcp/setup.js' from inside the sub-package directory (e.g. cd packages/mweb && node .catalyst/mcp/setup.js), NOT from the mono-repo root. The MCP will anchor to that sub-package as its project root.",
|
|
1053
|
+
"layer": "Framework",
|
|
1054
|
+
"source": "extracted:changelog",
|
|
1055
|
+
"tags": [
|
|
1056
|
+
"mono-repo",
|
|
1057
|
+
"monorepo",
|
|
1058
|
+
"setup",
|
|
1059
|
+
"canary.2",
|
|
1060
|
+
"project-structure"
|
|
1061
|
+
]
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
"section": "native_hooks",
|
|
1065
|
+
"title": "WebBridge imperative methods \u2014 source reference",
|
|
1066
|
+
"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",
|
|
1067
|
+
"layer": "Runtime",
|
|
1068
|
+
"source": "extracted:WebBridge.js",
|
|
1069
|
+
"tags": [
|
|
1070
|
+
"WebBridge",
|
|
1071
|
+
"getDeviceInfo",
|
|
1072
|
+
"openCamera",
|
|
1073
|
+
"imperative",
|
|
1074
|
+
"native-bridge",
|
|
1075
|
+
"source-reference",
|
|
1076
|
+
"node_modules/catalyst-core/src/native/bridge/WebBridge.js"
|
|
1077
|
+
]
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
"section": "native_hooks",
|
|
1081
|
+
"title": "Native hooks source \u2014 hooks/ directory",
|
|
1082
|
+
"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.",
|
|
1083
|
+
"layer": "Runtime",
|
|
1084
|
+
"source": "extracted:hooks/index.js",
|
|
1085
|
+
"tags": [
|
|
1086
|
+
"useCamera",
|
|
1087
|
+
"useFilePicker",
|
|
1088
|
+
"useIntent",
|
|
1089
|
+
"useGoogleSignIn",
|
|
1090
|
+
"useHapticFeedback",
|
|
1091
|
+
"useNotification",
|
|
1092
|
+
"useNetworkStatus",
|
|
1093
|
+
"useSafeArea",
|
|
1094
|
+
"useVideoStream",
|
|
1095
|
+
"useDeviceInfo",
|
|
1096
|
+
"useDataProtection",
|
|
1097
|
+
"native-hooks",
|
|
1098
|
+
"source-reference",
|
|
1099
|
+
"hooks-directory",
|
|
1100
|
+
"node_modules/catalyst-core/src/native/bridge/hooks.js",
|
|
1101
|
+
"node_modules/catalyst-core/src/native/bridge/hooks/"
|
|
1102
|
+
]
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"section": "native_hooks",
|
|
1106
|
+
"title": "NativeBridge utility \u2014 source reference",
|
|
1107
|
+
"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.",
|
|
1108
|
+
"layer": "Runtime",
|
|
1109
|
+
"source": "extracted:NativeBridge.js",
|
|
1110
|
+
"tags": [
|
|
1111
|
+
"NativeBridge",
|
|
1112
|
+
"postMessage",
|
|
1113
|
+
"register",
|
|
1114
|
+
"unregister",
|
|
1115
|
+
"native-bridge",
|
|
1116
|
+
"source-reference",
|
|
1117
|
+
"node_modules/catalyst-core/src/native/bridge/utils/NativeBridge.js"
|
|
1118
|
+
]
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
"section": "server_structure",
|
|
1122
|
+
"title": "SSR renderer \u2014 source reference",
|
|
1123
|
+
"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.",
|
|
1124
|
+
"layer": "Server",
|
|
1125
|
+
"source": "extracted:handler.js",
|
|
1126
|
+
"tags": [
|
|
1127
|
+
"renderer",
|
|
1128
|
+
"handler",
|
|
1129
|
+
"SSR",
|
|
1130
|
+
"renderToPipeableStream",
|
|
1131
|
+
"serverSideFunction",
|
|
1132
|
+
"source-reference",
|
|
1133
|
+
"node_modules/catalyst-core/src/server/renderer/handler.js",
|
|
1134
|
+
"rendertopipeable",
|
|
1135
|
+
"rendertopipeablestream",
|
|
1136
|
+
"pipeable"
|
|
1137
|
+
]
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
"section": "server_structure",
|
|
1141
|
+
"title": "renderToPipeableStream in catalyst \u2014 how and why",
|
|
1142
|
+
"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.",
|
|
1143
|
+
"layer": "Server",
|
|
1144
|
+
"source": "extracted:handler.js",
|
|
1145
|
+
"tags": [
|
|
1146
|
+
"renderToPipeableStream",
|
|
1147
|
+
"SSR",
|
|
1148
|
+
"streaming",
|
|
1149
|
+
"onShellReady",
|
|
1150
|
+
"onAllReady",
|
|
1151
|
+
"onError",
|
|
1152
|
+
"hydrateRoot",
|
|
1153
|
+
"pipe",
|
|
1154
|
+
"server-rendering",
|
|
1155
|
+
"rendertopipeable",
|
|
1156
|
+
"rendertopipeablestream",
|
|
1157
|
+
"pipeable",
|
|
1158
|
+
"streaming-ssr"
|
|
1159
|
+
]
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
"section": "server_structure",
|
|
1163
|
+
"title": "SSR pipeline \u2014 full request flow",
|
|
1164
|
+
"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.",
|
|
1165
|
+
"layer": "Server",
|
|
1166
|
+
"source": "extracted:handler.js",
|
|
1167
|
+
"tags": [
|
|
1168
|
+
"SSR",
|
|
1169
|
+
"server-side-function",
|
|
1170
|
+
"serverSideFunction",
|
|
1171
|
+
"request-flow",
|
|
1172
|
+
"pipeline",
|
|
1173
|
+
"data-fetching",
|
|
1174
|
+
"hydration",
|
|
1175
|
+
"CompleteDocument"
|
|
1176
|
+
]
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
"section": "bridge_architecture",
|
|
1180
|
+
"title": "getDeviceInfo \u2014 what it does and when to use it",
|
|
1181
|
+
"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).",
|
|
1182
|
+
"layer": "Runtime",
|
|
1183
|
+
"source": "extracted:WebBridge.js",
|
|
1184
|
+
"tags": [
|
|
1185
|
+
"getDeviceInfo",
|
|
1186
|
+
"WebBridge",
|
|
1187
|
+
"device-info",
|
|
1188
|
+
"imperative",
|
|
1189
|
+
"native-only",
|
|
1190
|
+
"isNative"
|
|
1191
|
+
]
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
"section": "config_structure",
|
|
1195
|
+
"title": "APPLICATION_UID \u2014 What it is and where to get it",
|
|
1196
|
+
"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.",
|
|
1197
|
+
"layer": "Config",
|
|
1198
|
+
"source": "static",
|
|
1199
|
+
"tags": [
|
|
1200
|
+
"config",
|
|
1201
|
+
"required",
|
|
1202
|
+
"APPLICATION_UID",
|
|
1203
|
+
"startup"
|
|
1204
|
+
]
|
|
1205
|
+
},
|
|
1206
|
+
{
|
|
1207
|
+
"section": "config_structure",
|
|
1208
|
+
"title": "Migration Phase 0 \u2014 Bare Minimum to Boot App (No Bridge)",
|
|
1209
|
+
"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.",
|
|
1210
|
+
"layer": "Config",
|
|
1211
|
+
"source": "static",
|
|
1212
|
+
"tags": [
|
|
1213
|
+
"migration",
|
|
1214
|
+
"bare-minimum",
|
|
1215
|
+
"config",
|
|
1216
|
+
"getting-started",
|
|
1217
|
+
"phase-0"
|
|
1218
|
+
]
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
"section": "bridge_architecture",
|
|
1222
|
+
"title": "Migrating from custom webviewkit bridge to catalyst hooks",
|
|
1223
|
+
"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.",
|
|
1224
|
+
"layer": "Bridge",
|
|
1225
|
+
"source": "static",
|
|
1226
|
+
"tags": [
|
|
1227
|
+
"migration",
|
|
1228
|
+
"webviewkit",
|
|
1229
|
+
"bridge",
|
|
1230
|
+
"WebBridge",
|
|
1231
|
+
"hooks",
|
|
1232
|
+
"isNative"
|
|
1233
|
+
]
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
"title": "App Icon File Placement (No Config Required)",
|
|
1237
|
+
"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.",
|
|
1238
|
+
"tags": [
|
|
1239
|
+
"app-icon",
|
|
1240
|
+
"icon",
|
|
1241
|
+
"assets",
|
|
1242
|
+
"android",
|
|
1243
|
+
"ios",
|
|
1244
|
+
"mipmap",
|
|
1245
|
+
"appicons",
|
|
1246
|
+
"launcher",
|
|
1247
|
+
"native",
|
|
1248
|
+
"public",
|
|
1249
|
+
"filesystem",
|
|
1250
|
+
"no-config"
|
|
1251
|
+
],
|
|
1252
|
+
"section": "native_assets",
|
|
1253
|
+
"layer": "Assets",
|
|
1254
|
+
"source": "static"
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
"section": "observability",
|
|
1258
|
+
"title": "Sentry Setup in Catalyst",
|
|
1259
|
+
"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.",
|
|
1260
|
+
"layer": "Observability",
|
|
1261
|
+
"source": "static",
|
|
1262
|
+
"tags": [
|
|
1263
|
+
"sentry",
|
|
1264
|
+
"error-tracking",
|
|
1265
|
+
"observability",
|
|
1266
|
+
"monitoring",
|
|
1267
|
+
"setup",
|
|
1268
|
+
"config",
|
|
1269
|
+
"init",
|
|
1270
|
+
"SENTRY_CONFIG",
|
|
1271
|
+
"integration"
|
|
1272
|
+
]
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
"section": "observability",
|
|
1276
|
+
"title": "OpenTelemetry Setup in Catalyst",
|
|
1277
|
+
"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.",
|
|
1278
|
+
"layer": "Observability",
|
|
1279
|
+
"source": "static",
|
|
1280
|
+
"tags": [
|
|
1281
|
+
"otel",
|
|
1282
|
+
"opentelemetry",
|
|
1283
|
+
"observability",
|
|
1284
|
+
"tracing",
|
|
1285
|
+
"metrics",
|
|
1286
|
+
"monitoring",
|
|
1287
|
+
"setup",
|
|
1288
|
+
"server",
|
|
1289
|
+
"preServerInit",
|
|
1290
|
+
"OTLP",
|
|
1291
|
+
"integration"
|
|
1292
|
+
]
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
"section": "observability",
|
|
1296
|
+
"title": "Sentry ErrorBoundary",
|
|
1297
|
+
"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.",
|
|
1298
|
+
"layer": "Observability",
|
|
1299
|
+
"source": "static",
|
|
1300
|
+
"tags": [
|
|
1301
|
+
"sentry",
|
|
1302
|
+
"error-boundary",
|
|
1303
|
+
"ErrorBoundary",
|
|
1304
|
+
"react",
|
|
1305
|
+
"error-handling",
|
|
1306
|
+
"observability"
|
|
1307
|
+
]
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
"section": "build_system",
|
|
1311
|
+
"title": "Webpack Customization via webpackConfig.js",
|
|
1312
|
+
"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.",
|
|
1313
|
+
"layer": "Build",
|
|
1314
|
+
"source": "static",
|
|
1315
|
+
"tags": [
|
|
1316
|
+
"webpack",
|
|
1317
|
+
"webpackConfig",
|
|
1318
|
+
"build",
|
|
1319
|
+
"customization",
|
|
1320
|
+
"plugins",
|
|
1321
|
+
"splitChunks",
|
|
1322
|
+
"clientPlugins",
|
|
1323
|
+
"ssrPlugins",
|
|
1324
|
+
"developmentPlugins",
|
|
1325
|
+
"transpileModules",
|
|
1326
|
+
"css-modules",
|
|
1327
|
+
"scss",
|
|
1328
|
+
"service-worker",
|
|
1329
|
+
"S3",
|
|
1330
|
+
"compression"
|
|
1331
|
+
]
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
"section": "build_system",
|
|
1335
|
+
"title": "React Compiler (opt-in via webpackConfig.js)",
|
|
1336
|
+
"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: '18' }\n reactCompiler: { target: '18' } // 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: '18' }.\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.",
|
|
1337
|
+
"layer": "Build",
|
|
1338
|
+
"source": "static",
|
|
1339
|
+
"tags": [
|
|
1340
|
+
"react-compiler",
|
|
1341
|
+
"babel",
|
|
1342
|
+
"webpackConfig",
|
|
1343
|
+
"opt-in",
|
|
1344
|
+
"build",
|
|
1345
|
+
"babel-plugin-react-compiler",
|
|
1346
|
+
"performance",
|
|
1347
|
+
"react"
|
|
1348
|
+
]
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
"section": "build_system",
|
|
1352
|
+
"title": "CLI Commands and Package Scripts",
|
|
1353
|
+
"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.",
|
|
1354
|
+
"layer": "Build",
|
|
1355
|
+
"source": "static",
|
|
1356
|
+
"tags": [
|
|
1357
|
+
"cli",
|
|
1358
|
+
"catalyst",
|
|
1359
|
+
"scripts",
|
|
1360
|
+
"build",
|
|
1361
|
+
"start",
|
|
1362
|
+
"serve",
|
|
1363
|
+
"devBuild",
|
|
1364
|
+
"devServe",
|
|
1365
|
+
"buildApp",
|
|
1366
|
+
"setupEmulator",
|
|
1367
|
+
"native",
|
|
1368
|
+
"commands",
|
|
1369
|
+
"bin"
|
|
1370
|
+
]
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"section": "framework_identity",
|
|
1374
|
+
"title": "Project File Conventions and Structure",
|
|
1375
|
+
"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/.",
|
|
1376
|
+
"layer": "Framework",
|
|
1377
|
+
"source": "static",
|
|
1378
|
+
"tags": [
|
|
1379
|
+
"project-structure",
|
|
1380
|
+
"file-conventions",
|
|
1381
|
+
"layout",
|
|
1382
|
+
"routes",
|
|
1383
|
+
"pages",
|
|
1384
|
+
"containers",
|
|
1385
|
+
"components",
|
|
1386
|
+
"client",
|
|
1387
|
+
"server",
|
|
1388
|
+
"preServerInit",
|
|
1389
|
+
"moduleAliases",
|
|
1390
|
+
"loadable",
|
|
1391
|
+
"css-modules",
|
|
1392
|
+
"config"
|
|
1393
|
+
]
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
"section": "native_hooks",
|
|
1397
|
+
"title": "Native Hooks \u2014 Full List and Standard Interface",
|
|
1398
|
+
"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.",
|
|
1399
|
+
"layer": "Bridge",
|
|
1400
|
+
"source": "static",
|
|
1401
|
+
"tags": [
|
|
1402
|
+
"native-hooks",
|
|
1403
|
+
"hooks",
|
|
1404
|
+
"useCamera",
|
|
1405
|
+
"useFilePicker",
|
|
1406
|
+
"useIntent",
|
|
1407
|
+
"useNotification",
|
|
1408
|
+
"useNetworkStatus",
|
|
1409
|
+
"useSafeArea",
|
|
1410
|
+
"useHapticFeedback",
|
|
1411
|
+
"useDataProtection",
|
|
1412
|
+
"useCameraPermission",
|
|
1413
|
+
"useNotificationPermission",
|
|
1414
|
+
"useGoogleSignIn",
|
|
1415
|
+
"useVideoStream",
|
|
1416
|
+
"useDeviceInfo",
|
|
1417
|
+
"useBaseHook",
|
|
1418
|
+
"web-fallback",
|
|
1419
|
+
"webFallbackActive",
|
|
1420
|
+
"setWebFallback",
|
|
1421
|
+
"native",
|
|
1422
|
+
"bridge"
|
|
1423
|
+
],
|
|
1424
|
+
"github_files": [
|
|
1425
|
+
"src/native/bridge/hooks/index.js",
|
|
1426
|
+
"src/native/bridge/hooks/useCamera.js",
|
|
1427
|
+
"src/native/bridge/hooks/useVideoStream.js",
|
|
1428
|
+
"src/native/bridge/useBaseHook.js"
|
|
1429
|
+
]
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
"section": "native_hooks",
|
|
1433
|
+
"title": "useVideoStream \u2014 return shape",
|
|
1434
|
+
"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.",
|
|
1435
|
+
"layer": "Runtime",
|
|
1436
|
+
"source": "static",
|
|
1437
|
+
"tags": [
|
|
1438
|
+
"native-hooks",
|
|
1439
|
+
"useVideoStream",
|
|
1440
|
+
"return-shape",
|
|
1441
|
+
"web-fallback",
|
|
1442
|
+
"getUserMedia",
|
|
1443
|
+
"MediaStream",
|
|
1444
|
+
"sendCommand",
|
|
1445
|
+
"secure-context",
|
|
1446
|
+
"rules-of-hooks"
|
|
1447
|
+
]
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
"section": "native_hooks",
|
|
1451
|
+
"title": "useDeviceInfo \u2014 return shape",
|
|
1452
|
+
"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).",
|
|
1453
|
+
"layer": "Runtime",
|
|
1454
|
+
"source": "static",
|
|
1455
|
+
"tags": [
|
|
1456
|
+
"native-hooks",
|
|
1457
|
+
"useDeviceInfo",
|
|
1458
|
+
"return-shape",
|
|
1459
|
+
"web-fallback",
|
|
1460
|
+
"device-info",
|
|
1461
|
+
"navigator",
|
|
1462
|
+
"screen"
|
|
1463
|
+
]
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
"section": "native_hooks",
|
|
1467
|
+
"title": "useDataProtection \u2014 return shape",
|
|
1468
|
+
"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.",
|
|
1469
|
+
"layer": "Runtime",
|
|
1470
|
+
"source": "static",
|
|
1471
|
+
"tags": [
|
|
1472
|
+
"native-hooks",
|
|
1473
|
+
"useDataProtection",
|
|
1474
|
+
"return-shape",
|
|
1475
|
+
"web-fallback",
|
|
1476
|
+
"setScreenSecure",
|
|
1477
|
+
"clearWebData",
|
|
1478
|
+
"security",
|
|
1479
|
+
"overlay",
|
|
1480
|
+
"IndexedDB"
|
|
1481
|
+
]
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
"section": "seo_metadata",
|
|
1485
|
+
"title": "Server-Side Meta Tags \u2014 setMetaData()",
|
|
1486
|
+
"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.",
|
|
1487
|
+
"layer": "Component",
|
|
1488
|
+
"source": "static",
|
|
1489
|
+
"tags": [
|
|
1490
|
+
"seo",
|
|
1491
|
+
"meta",
|
|
1492
|
+
"metadata",
|
|
1493
|
+
"setMetaData",
|
|
1494
|
+
"title",
|
|
1495
|
+
"og",
|
|
1496
|
+
"open-graph",
|
|
1497
|
+
"head",
|
|
1498
|
+
"ssr",
|
|
1499
|
+
"server-side"
|
|
1500
|
+
]
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
"section": "seo_metadata",
|
|
1504
|
+
"title": "Client-Side Meta Updates \u2014 MetaTag + custom document",
|
|
1505
|
+
"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.",
|
|
1506
|
+
"layer": "Component",
|
|
1507
|
+
"source": "static",
|
|
1508
|
+
"tags": [
|
|
1509
|
+
"seo",
|
|
1510
|
+
"meta",
|
|
1511
|
+
"metadata",
|
|
1512
|
+
"MetaTag",
|
|
1513
|
+
"document",
|
|
1514
|
+
"custom-document",
|
|
1515
|
+
"head",
|
|
1516
|
+
"client-side",
|
|
1517
|
+
"fonts",
|
|
1518
|
+
"preconnect"
|
|
1519
|
+
]
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
"section": "config_structure",
|
|
1523
|
+
"title": "notifications Config \u2014 Required to Enable Android Permission",
|
|
1524
|
+
"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.",
|
|
1525
|
+
"layer": "Config",
|
|
1526
|
+
"source": "extracted:src/native/buildAppAndroid.js",
|
|
1527
|
+
"tags": [
|
|
1528
|
+
"config",
|
|
1529
|
+
"notifications",
|
|
1530
|
+
"android",
|
|
1531
|
+
"permissions",
|
|
1532
|
+
"POST_NOTIFICATIONS"
|
|
1533
|
+
]
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
"section": "config_structure",
|
|
1537
|
+
"title": "Notification Sound File \u2014 Required Asset for Android Build",
|
|
1538
|
+
"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.",
|
|
1539
|
+
"layer": "Config",
|
|
1540
|
+
"source": "extracted:src/native/buildAppAndroid.js",
|
|
1541
|
+
"tags": [
|
|
1542
|
+
"config",
|
|
1543
|
+
"notifications",
|
|
1544
|
+
"android",
|
|
1545
|
+
"assets",
|
|
1546
|
+
"build-error",
|
|
1547
|
+
"raw",
|
|
1548
|
+
"sound"
|
|
1549
|
+
]
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
"section": "config_structure",
|
|
1553
|
+
"title": "googleSignIn Config \u2014 Required to Enable Google Sign-In",
|
|
1554
|
+
"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.",
|
|
1555
|
+
"layer": "Config",
|
|
1556
|
+
"source": "extracted:src/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt",
|
|
1557
|
+
"tags": [
|
|
1558
|
+
"config",
|
|
1559
|
+
"googleSignIn",
|
|
1560
|
+
"android",
|
|
1561
|
+
"oauth",
|
|
1562
|
+
"sign-in"
|
|
1563
|
+
]
|
|
1564
|
+
}
|
|
1565
|
+
]
|