catalyst-core-internal 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +10 -2
- package/dist/native/bridge/hooks.js +4 -4
- package/dist/native/bridge/useBaseHook.js +3 -4
- package/dist/native/bridge/utils/NativeBridge.js +4 -4
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +2 -13
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +0 -36
- package/dist/native/iosnativeWebView/iosnativeWebView.xctestplan +0 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FrameworkServerUtilsTests.swift +4 -14
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +21 -9
- package/mcp_v2/conversion-tasks.json +371 -0
- package/mcp_v2/knowledge-base.json +1450 -0
- package/mcp_v2/lib/helpers.js +145 -0
- package/mcp_v2/mcp.js +366 -0
- package/mcp_v2/package.json +13 -0
- package/mcp_v2/schema.sql +88 -0
- package/mcp_v2/setup.js +262 -0
- package/mcp_v2/tools/build.js +449 -0
- package/mcp_v2/tools/config.js +262 -0
- package/mcp_v2/tools/conversion.js +492 -0
- package/mcp_v2/tools/debug.js +62 -0
- package/mcp_v2/tools/knowledge.js +213 -0
- package/mcp_v2/tools/sync.js +21 -0
- package/mcp_v2/tools/tasks.js +844 -0
- package/package.json +1 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/SecurityBridgeTest.kt +0 -199
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeCommandHandlerSecurityTests.swift +0 -212
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ScreenSecureManagerTests.swift +0 -121
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "T1_CONFIG",
|
|
4
|
+
"tier": 1,
|
|
5
|
+
"title": "config/config.json exists with required fields",
|
|
6
|
+
"detect_pattern": "file_exists + field_check",
|
|
7
|
+
"how_to_check": [
|
|
8
|
+
"file: config/config.json must exist",
|
|
9
|
+
"field: NODE_SERVER_PORT must be present",
|
|
10
|
+
"field: WEBVIEW_CONFIG must be present",
|
|
11
|
+
"field: API_URL must be present"
|
|
12
|
+
],
|
|
13
|
+
"fix_guide": "Create config/config.json with: { NODE_SERVER_PORT: 3000, API_URL: 'https://api.example.com', CLIENT_ENV_KEYS: [], PUBLIC_STATIC_ASSET_URL: '', WEBVIEW_CONFIG: { port: 3000, accessControl: { enabled: true, allowedUrls: [] } } }",
|
|
14
|
+
"verify_hint": "Run `npm run devServe`. Server should start without 'Cannot find module config' or 'missing required field' errors. Check terminal output — catalyst-core logs the config it loaded at boot.",
|
|
15
|
+
"depends_on": []
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "T2_ROUTER_DEP",
|
|
19
|
+
"tier": 1,
|
|
20
|
+
"title": "@tata1mg/router installed in package.json",
|
|
21
|
+
"detect_pattern": "package_json_dep",
|
|
22
|
+
"how_to_check": [
|
|
23
|
+
"package.json dependencies or devDependencies must include @tata1mg/router"
|
|
24
|
+
],
|
|
25
|
+
"fix_guide": "Run: npm install @tata1mg/router. Then replace all react-router-dom imports with @tata1mg/router. Replace BrowserRouter/Routes/Route setup with RouterDataProvider + routes array in src/js/routes/index.js.",
|
|
26
|
+
"verify_hint": "Run `npm run devServe`. No 'Cannot resolve @tata1mg/router' errors in build output. If react-router-dom was the previous router, confirm no leftover BrowserRouter/Routes imports remain — they will conflict.",
|
|
27
|
+
"depends_on": [
|
|
28
|
+
"T1_CONFIG"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "T3_ROUTES_FILE",
|
|
33
|
+
"tier": 1,
|
|
34
|
+
"title": "src/js/routes/index.js exists with route array",
|
|
35
|
+
"detect_pattern": "file_exists + code_pattern",
|
|
36
|
+
"how_to_check": [
|
|
37
|
+
"file: src/js/routes/index.js must exist",
|
|
38
|
+
"pattern: file must export an array (export default [ or module.exports = [)"
|
|
39
|
+
],
|
|
40
|
+
"fix_guide": "Create src/js/routes/index.js: import HomePage from '../pages/Home'; export default [{ path: '/', component: HomePage, exact: true }]. Add one entry per page component. Each component needs serverFetcher/clientFetcher attached (see T4_DATA_FETCHING).",
|
|
41
|
+
"verify_hint": "Run `npm run devServe` and open the first route in browser (e.g. http://localhost:3000/). Page should render — not a blank screen or 404. Check browser console for 'No routes matched' warnings which mean the route array is empty or paths are wrong.",
|
|
42
|
+
"depends_on": [
|
|
43
|
+
"T2_ROUTER_DEP"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "T4_DATA_FETCHING",
|
|
48
|
+
"tier": 1,
|
|
49
|
+
"title": "Page components use serverFetcher/clientFetcher (not useEffect for data)",
|
|
50
|
+
"detect_pattern": "signal_collection + llm_review",
|
|
51
|
+
"review_question": "Do the files using useEffect for data fetching represent unconverted page-level data loading, or are they legitimate non-page effects (timers, subscriptions, animations)?",
|
|
52
|
+
"what_correct_looks_like": "Page components have .serverFetcher or .clientFetcher static functions attached. useEffect is only used for side effects (subscriptions, DOM mutations, timers), not initial data loads.",
|
|
53
|
+
"what_gap_looks_like": "Page components (in src/js/pages/ or src/js/containers/) call fetch/axios inside useEffect to load initial data, with no .serverFetcher or .clientFetcher attached.",
|
|
54
|
+
"how_to_check": [
|
|
55
|
+
"scan: src/js/pages/**/*.js and src/js/containers/**/*.js",
|
|
56
|
+
"signal: files with useEffect + fetch/axios patterns (possible unconverted data loading)",
|
|
57
|
+
"signal: files with .serverFetcher = or .clientFetcher = (converted pages)",
|
|
58
|
+
"llm judges: is any useEffect+fetch file a page component that needs conversion?"
|
|
59
|
+
],
|
|
60
|
+
"fix_guide": "For each page component: 1) Remove useState+useEffect data loading. 2) Attach static function: HomePage.serverFetcher = async ({ params, searchParams }, { store }) => { return await fetchApi(); }. 3) In component body: const data = useCurrentRouteData() from @tata1mg/router. This is a full rewrite of every page's data layer.",
|
|
61
|
+
"verify_hint": "Open a migrated page in browser. Open DevTools Network tab — on first load there should be NO XHR/fetch calls for page data (data arrives server-rendered in HTML). View Page Source and confirm the page content (not just a loading skeleton) is present in the raw HTML. If content is missing from source, serverFetcher is not attached or not running.",
|
|
62
|
+
"depends_on": [
|
|
63
|
+
"T3_ROUTES_FILE"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "T5_ROUTER_DATA_PROVIDER",
|
|
68
|
+
"tier": 1,
|
|
69
|
+
"title": "RouterDataProvider wraps app in src/js/routes/utils.js",
|
|
70
|
+
"detect_pattern": "file_exists + code_pattern",
|
|
71
|
+
"how_to_check": [
|
|
72
|
+
"file: src/js/routes/utils.js must exist",
|
|
73
|
+
"pattern: RouterDataProvider must be imported and used in utils.js"
|
|
74
|
+
],
|
|
75
|
+
"fix_guide": "Create src/js/routes/utils.js: import { RouterDataProvider } from '@tata1mg/router'; import routes from './index'; export default function AppRouter() { return <RouterDataProvider routes={routes} />; }. Without this, useCurrentRouteData() returns undefined everywhere.",
|
|
76
|
+
"verify_hint": "In a page component that uses useCurrentRouteData(), console.log the return value. It should be the data object returned by serverFetcher — not undefined. If undefined, RouterDataProvider is not wrapping the tree or the route entry is missing the component reference.",
|
|
77
|
+
"depends_on": [
|
|
78
|
+
"T3_ROUTES_FILE"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "T6_APP_SHELL",
|
|
83
|
+
"tier": 1,
|
|
84
|
+
"title": "App shell renders <Outlet /> from @tata1mg/router",
|
|
85
|
+
"detect_pattern": "file_exists + code_pattern",
|
|
86
|
+
"how_to_check": [
|
|
87
|
+
"file: src/js/containers/App/index.js must exist",
|
|
88
|
+
"pattern: Outlet must be imported from @tata1mg/router",
|
|
89
|
+
"pattern: <Outlet /> must be rendered in the component"
|
|
90
|
+
],
|
|
91
|
+
"fix_guide": "In src/js/containers/App/index.js: import { Outlet } from '@tata1mg/router'. Render <Outlet /> where route content should appear (replacing any <Switch> or <Routes> block). Keep global layout (nav, footer) around it.",
|
|
92
|
+
"verify_hint": "Open the app in browser. Global layout (header, footer, nav) should appear on every page. The area where page content changes should not be blank. If page content area is empty, <Outlet /> is missing or placed incorrectly in the App shell.",
|
|
93
|
+
"depends_on": [
|
|
94
|
+
"T5_ROUTER_DATA_PROVIDER"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "T7_SERVER_FILES",
|
|
99
|
+
"tier": 1,
|
|
100
|
+
"title": "server/ directory has all 3 required files",
|
|
101
|
+
"detect_pattern": "file_exists",
|
|
102
|
+
"how_to_check": [
|
|
103
|
+
"file: server/index.js must exist",
|
|
104
|
+
"file: server/server.js must exist",
|
|
105
|
+
"file: server/document.js must exist"
|
|
106
|
+
],
|
|
107
|
+
"fix_guide": "Create all 3 files: (1) server/index.js — export default { preServerInit: async () => {}, onRouteMatch: async (req, res) => {}, onServerError: (err) => {} }. (2) server/server.js — export const addMiddlewares = (app) => { /* add express middleware here */ }. (3) server/document.js — export default ({ html, head, scripts }) => `<!DOCTYPE html><html><head>${head}</head><body><div id='root'>${html}</div>${scripts}</body></html>`.",
|
|
108
|
+
"verify_hint": "Run `npm run devServe`. Server must start without 'Cannot find module ./server/index' errors. Hit http://localhost:3000/ — the response HTML should contain your document.js shell structure (check View Source). A blank body or missing <div id='root'> means document.js is not returning valid HTML.",
|
|
109
|
+
"depends_on": [
|
|
110
|
+
"T1_CONFIG"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"id": "T8_CLIENT_ENTRY",
|
|
115
|
+
"tier": 1,
|
|
116
|
+
"title": "client/index.js bootstraps app with WebBridge.init() and hydrateRoot",
|
|
117
|
+
"detect_pattern": "file_exists + code_pattern",
|
|
118
|
+
"how_to_check": [
|
|
119
|
+
"file: client/index.js must exist",
|
|
120
|
+
"file: client/styles.js must exist",
|
|
121
|
+
"pattern: client/index.js must import WebBridge from 'catalyst-core/WebBridge'",
|
|
122
|
+
"pattern: client/index.js must call WebBridge.init()",
|
|
123
|
+
"pattern: client/index.js must use hydrateRoot from 'react-dom/client' (not ReactDOM.render or hydrate)",
|
|
124
|
+
"pattern: client/index.js must import clientRouter from 'catalyst-core/router/ClientRouter'",
|
|
125
|
+
"pattern: client/index.js must wrap app in RouterProvider from '@tata1mg/router'"
|
|
126
|
+
],
|
|
127
|
+
"fix_guide": "Create client/index.js:\nimport React from 'react';\nimport './styles';\nimport { hydrateRoot } from 'react-dom/client';\nimport { loadableReady } from '@loadable/component';\nimport { Provider } from 'react-redux';\nimport { RouterProvider } from '@tata1mg/router';\nimport { HelmetProvider } from 'react-helmet-async';\nimport clientRouter from 'catalyst-core/router/ClientRouter';\nimport WebBridge from 'catalyst-core/WebBridge';\nimport configureStore from '@store';\n\nwindow.addEventListener('load', () => {\n loadableReady(() => {\n const { __ROUTER_INITIAL_DATA__: routerInitialData, __INITIAL_STATE__ } = window;\n const store = configureStore(__INITIAL_STATE__ || {});\n const router = clientRouter({ store, routerInitialState: routerInitialData });\n const Application = (\n <Provider store={store} serverState={__INITIAL_STATE__}>\n <HelmetProvider>\n <React.StrictMode>\n <RouterProvider router={router} />\n </React.StrictMode>\n </HelmetProvider>\n </Provider>\n );\n WebBridge.init();\n const container = document.getElementById('app');\n hydrateRoot(container, Application);\n });\n});\n\nCreate client/styles.js: import '../src/styles/global.css' (adjust path to your global stylesheet).\n\nCRITICAL: WebBridge.init() must be called before hydrateRoot — it registers native bridge event handlers. Missing this call means no JS-to-native communication (postMessage, camera, file picker, haptics all break silently on device).",
|
|
128
|
+
"verify_hint": "Open the app in browser. Open DevTools Console — there should be no 'hydrateRoot' mismatch warnings (if there are, server HTML and client render differ). Open DevTools Elements — the #app or #root div should have React content inside it (not empty). Click an interactive element — if nothing responds, hydration failed. Also confirm global styles are applied (CSS loads).",
|
|
129
|
+
"depends_on": [
|
|
130
|
+
"T6_APP_SHELL"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "T7b_NATIVE_SCRIPTS",
|
|
135
|
+
"tier": 1,
|
|
136
|
+
"title": "package.json has catalyst native build and emulator scripts",
|
|
137
|
+
"detect_pattern": "package_json_scripts",
|
|
138
|
+
"how_to_check": [
|
|
139
|
+
"package.json scripts must include: buildApp",
|
|
140
|
+
"package.json scripts must include: buildApp:ios",
|
|
141
|
+
"package.json scripts must include: buildApp:android",
|
|
142
|
+
"package.json scripts must include: setupEmulator",
|
|
143
|
+
"package.json scripts must include: setupEmulator:ios",
|
|
144
|
+
"package.json scripts must include: setupEmulator:android"
|
|
145
|
+
],
|
|
146
|
+
"fix_guide": "Add to package.json scripts:\n\"devBuild\": \"catalyst devBuild\",\n\"devServe\": \"catalyst devServe\",\n\"buildApp\": \"catalyst buildApp\",\n\"buildApp:ios\": \"catalyst buildApp:ios\",\n\"buildApp:android\": \"catalyst buildApp:android\",\n\"setupEmulator\": \"catalyst setupEmulator\",\n\"setupEmulator:ios\": \"catalyst setupEmulator:ios\",\n\"setupEmulator:android\": \"catalyst setupEmulator:android\"\n\nThese scripts are required to run native builds and launch emulators. Without them, 'npm run buildApp:android' and MCP build flow commands will fail with 'missing script' errors.",
|
|
147
|
+
"verify_hint": "Run `npm run devBuild` — should start webpack compilation without 'missing script' error. Run `npm run devServe` — server should start on NODE_SERVER_PORT. Both commands should be found by npm, not fail with 'ERR_INVALID_ARG_TYPE' or 'script not found'.",
|
|
148
|
+
"depends_on": [
|
|
149
|
+
"T1_CONFIG"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"id": "T9_WEBVIEW_ANDROID",
|
|
154
|
+
"tier": 2,
|
|
155
|
+
"title": "WEBVIEW_CONFIG has android block with required fields",
|
|
156
|
+
"detect_pattern": "field_check",
|
|
157
|
+
"how_to_check": [
|
|
158
|
+
"file: config/config.json",
|
|
159
|
+
"field: WEBVIEW_CONFIG.android must exist",
|
|
160
|
+
"field: WEBVIEW_CONFIG.android.buildType must exist",
|
|
161
|
+
"field: WEBVIEW_CONFIG.android.sdkPath must exist",
|
|
162
|
+
"field: WEBVIEW_CONFIG.android.emulatorName must exist",
|
|
163
|
+
"field: WEBVIEW_CONFIG.android.appName must exist"
|
|
164
|
+
],
|
|
165
|
+
"fix_guide": "Add to WEBVIEW_CONFIG in config/config.json: android: { buildType: 'debug', sdkPath: '/Users/yourname/Android/sdk', emulatorName: 'Pixel_5_API_30', appName: 'MyApp', cachePattern: '*.css,*.js,*.png,*.svg,*.jpg' }. sdkPath must be absolute path to your Android SDK.",
|
|
166
|
+
"verify_hint": "Run `npm run buildApp:android`. Build should start without 'sdkPath not found' or 'missing android config' errors. If sdkPath is wrong, you'll see an immediate error — run `echo $ANDROID_HOME` in terminal to find the correct path.",
|
|
167
|
+
"depends_on": [
|
|
168
|
+
"T1_CONFIG"
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"id": "T10_WEBVIEW_IOS",
|
|
173
|
+
"tier": 2,
|
|
174
|
+
"title": "WEBVIEW_CONFIG has ios block with required fields",
|
|
175
|
+
"detect_pattern": "field_check",
|
|
176
|
+
"how_to_check": [
|
|
177
|
+
"file: config/config.json",
|
|
178
|
+
"field: WEBVIEW_CONFIG.ios must exist",
|
|
179
|
+
"field: WEBVIEW_CONFIG.ios.buildType must exist",
|
|
180
|
+
"field: WEBVIEW_CONFIG.ios.appBundleId must exist",
|
|
181
|
+
"field: WEBVIEW_CONFIG.ios.simulatorName must exist",
|
|
182
|
+
"field: WEBVIEW_CONFIG.ios.appName must exist"
|
|
183
|
+
],
|
|
184
|
+
"fix_guide": "Add to WEBVIEW_CONFIG in config/config.json: ios: { buildType: 'Debug', appBundleId: 'com.company.appname', simulatorName: 'iPhone 15', appName: 'MyApp', cachePattern: '*.css,*.js,*.png,*.svg,*.jpg' }. appBundleId must match your Apple Developer provisioning profile.",
|
|
185
|
+
"verify_hint": "Run `npm run buildApp:ios`. Build should start Xcode compilation without 'missing ios config' or 'bundle ID not found' errors. If simulatorName is wrong, run `xcrun simctl list devices` to see available simulators and update the field.",
|
|
186
|
+
"depends_on": [
|
|
187
|
+
"T1_CONFIG"
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": "T11_ACCESS_CONTROL",
|
|
192
|
+
"tier": 2,
|
|
193
|
+
"title": "WEBVIEW_CONFIG.accessControl is configured",
|
|
194
|
+
"detect_pattern": "field_check",
|
|
195
|
+
"how_to_check": [
|
|
196
|
+
"file: config/config.json",
|
|
197
|
+
"field: WEBVIEW_CONFIG.accessControl must exist",
|
|
198
|
+
"field: WEBVIEW_CONFIG.accessControl.enabled must be true",
|
|
199
|
+
"field: WEBVIEW_CONFIG.accessControl.allowedUrls must be a non-empty array"
|
|
200
|
+
],
|
|
201
|
+
"fix_guide": "Add to WEBVIEW_CONFIG: accessControl: { enabled: true, allowedUrls: ['*.yourdomain.com*', 'http://localhost:*', 'https://localhost:*'] }. Warning: if allowedUrls is defined but empty, ALL URLs are blocked including your own API. Always include http://localhost:* for dev.",
|
|
202
|
+
"verify_hint": "Open app on device/emulator and trigger an API call. If the response never comes back and the network tab shows the request was blocked, allowedUrls is missing your API domain. Add '*.yourdomain.com*' to allowedUrls and rebuild. On first run, check native logs (adb logcat / Xcode console) for 'URL blocked' messages.",
|
|
203
|
+
"depends_on": [
|
|
204
|
+
"T1_CONFIG"
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"id": "T12_SPLASH_SCREEN",
|
|
209
|
+
"tier": 2,
|
|
210
|
+
"title": "splashScreen config + asset files exist",
|
|
211
|
+
"detect_pattern": "field_check + file_exists",
|
|
212
|
+
"how_to_check": [
|
|
213
|
+
"file: config/config.json",
|
|
214
|
+
"field: splashScreen must exist at top level (NOT inside WEBVIEW_CONFIG)",
|
|
215
|
+
"file: public/android/splashscreen.png must exist",
|
|
216
|
+
"file: public/ios/splashscreen.png must exist"
|
|
217
|
+
],
|
|
218
|
+
"fix_guide": "1) Add to top level of config/config.json (NOT inside WEBVIEW_CONFIG): splashScreen: { android: { path: 'public/android/splashscreen.png' }, ios: { path: 'public/ios/splashscreen.png' } }. 2) Place splash images at those paths. Recommended sizes: Android 1080x1920px, iOS 1242x2688px.",
|
|
219
|
+
"verify_hint": "Build and install the app on a device/emulator. On cold launch (after force-closing), you should see the splash image for ~1-2 seconds before the app loads. If you see a white flash instead, either the asset path is wrong or splashScreen is inside WEBVIEW_CONFIG instead of at the top level of config.json.",
|
|
220
|
+
"depends_on": [
|
|
221
|
+
"T1_CONFIG"
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "T13_ANDROID_ICONS",
|
|
226
|
+
"tier": 2,
|
|
227
|
+
"title": "Android app icons — 5 density files in public/android/appIcons/",
|
|
228
|
+
"detect_pattern": "file_exists",
|
|
229
|
+
"how_to_check": [
|
|
230
|
+
"file: public/android/appIcons/mdpi.png must exist",
|
|
231
|
+
"file: public/android/appIcons/hdpi.png must exist",
|
|
232
|
+
"file: public/android/appIcons/xhdpi.png must exist",
|
|
233
|
+
"file: public/android/appIcons/xxhdpi.png must exist",
|
|
234
|
+
"file: public/android/appIcons/xxxhdpi.png must exist"
|
|
235
|
+
],
|
|
236
|
+
"fix_guide": "Place PNG app icons at public/android/appIcons/ with these exact filenames and sizes: mdpi.png (48x48), hdpi.png (72x72), xhdpi.png (96x96), xxhdpi.png (144x144), xxxhdpi.png (192x192). All must be square with transparent background.",
|
|
237
|
+
"verify_hint": "After `npm run buildApp:android`, install the APK on a device. Check the home screen / app drawer — your app icon should appear (not the default catalyst placeholder icon). If you still see the placeholder, the filenames are wrong — they must be exactly mdpi.png, hdpi.png etc. (not icon-mdpi.png).",
|
|
238
|
+
"depends_on": [
|
|
239
|
+
"T9_WEBVIEW_ANDROID"
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"id": "T14_IOS_ICONS",
|
|
244
|
+
"tier": 2,
|
|
245
|
+
"title": "iOS app icons — 9 size files in public/ios/appIcons/",
|
|
246
|
+
"detect_pattern": "file_exists",
|
|
247
|
+
"how_to_check": [
|
|
248
|
+
"file: public/ios/appIcons/20.png must exist",
|
|
249
|
+
"file: public/ios/appIcons/29.png must exist",
|
|
250
|
+
"file: public/ios/appIcons/40.png must exist",
|
|
251
|
+
"file: public/ios/appIcons/58.png must exist",
|
|
252
|
+
"file: public/ios/appIcons/60.png must exist",
|
|
253
|
+
"file: public/ios/appIcons/80.png must exist",
|
|
254
|
+
"file: public/ios/appIcons/87.png must exist",
|
|
255
|
+
"file: public/ios/appIcons/120.png must exist",
|
|
256
|
+
"file: public/ios/appIcons/1024.png must exist"
|
|
257
|
+
],
|
|
258
|
+
"fix_guide": "Place PNG app icons at public/ios/appIcons/ named by their pixel size (e.g. 120.png = 120x120px). Required sizes: 20, 29, 40, 58, 60, 80, 87, 120, 1024. No alpha/transparency — iOS rejects icons with transparency. Use solid background.",
|
|
259
|
+
"verify_hint": "After `npm run buildApp:ios`, install on simulator. Check home screen — your icon should appear. If Xcode build fails with 'Icon files are missing' or 'PNG file is corrupt', the images have transparency (iOS forbids it) or filenames are wrong. Run `xcrun simctl install booted <app.app>` to install manually if needed.",
|
|
260
|
+
"depends_on": [
|
|
261
|
+
"T10_WEBVIEW_IOS"
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"id": "T15_OFFLINE_HTML",
|
|
266
|
+
"tier": 2,
|
|
267
|
+
"title": "public/offline.html exists",
|
|
268
|
+
"detect_pattern": "file_exists",
|
|
269
|
+
"how_to_check": [
|
|
270
|
+
"file: public/offline.html must exist"
|
|
271
|
+
],
|
|
272
|
+
"fix_guide": "Create public/offline.html — shown when device is offline. Minimum: <!DOCTYPE html><html><body><h1>No internet connection</h1><p>Please check your network and try again.</p></body></html>. Can be styled to match app branding.",
|
|
273
|
+
"verify_hint": "On a real device with the app running, turn on Airplane mode. The app should show the offline.html content instead of a system 'No internet' error page or blank screen. If you still see the system error page, the file path is wrong or the build did not pick it up.",
|
|
274
|
+
"depends_on": [
|
|
275
|
+
"T1_CONFIG"
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"id": "T17a_USE_FILEPICKER",
|
|
280
|
+
"tier": 3,
|
|
281
|
+
"title": "File inputs replaced with useFilePicker (native file picking)",
|
|
282
|
+
"detect_pattern": "signal_collection + llm_review",
|
|
283
|
+
"review_question": "Do files with <input type='file'> have a native branch using useFilePicker, or is the hook used somewhere in the component tree (parent passes execute/isNative as props)?",
|
|
284
|
+
"what_correct_looks_like": "Component uses useFilePicker from catalyst-core/hooks. Checks isNative and calls execute() on native. May keep <input type='file'> as web fallback only. Hook may live in a parent and be passed down as props.",
|
|
285
|
+
"what_gap_looks_like": "<input type='file'> used directly with no useFilePicker import anywhere in the component tree. No isNative check. No native branch.",
|
|
286
|
+
"how_to_check": [
|
|
287
|
+
"signal old_pattern: files with <input[^>]+type=['\"]file['\"]",
|
|
288
|
+
"signal hook_usage: files importing useFilePicker from catalyst-core/hooks",
|
|
289
|
+
"llm judges: for each file with input type=file, does useFilePicker exist in it or a parent component?"
|
|
290
|
+
],
|
|
291
|
+
"fix_guide": "Replace <input type='file'> with useFilePicker. import { useFilePicker } from 'catalyst-core/hooks'. const { isNative, execute, data, error } = useFilePicker(); On native: <button onClick={execute}>Pick File</button>. Keep <input type='file'> as web-only fallback: if (!isNative) return <input type='file' onChange={...} />.",
|
|
292
|
+
"verify_hint": "On device/emulator, tap the file pick button. Native file picker sheet should open (system file browser on Android, Files app on iOS). If nothing happens, WebBridge is not initialized (check T8_CLIENT_ENTRY) or the hook execute() is not being called. On web, the regular file input should still work.",
|
|
293
|
+
"depends_on": [
|
|
294
|
+
"T8_CLIENT_ENTRY"
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"id": "T17b_USE_CAMERA",
|
|
299
|
+
"tier": 3,
|
|
300
|
+
"title": "Camera inputs replaced with useCamera (native camera capture)",
|
|
301
|
+
"detect_pattern": "signal_collection + llm_review",
|
|
302
|
+
"review_question": "Do files with <input type='file' accept='image/*' capture> have a native branch using useCamera, or is the hook used in the component tree?",
|
|
303
|
+
"what_correct_looks_like": "Component uses useCamera from catalyst-core/hooks. Checks isNative and calls takePhoto() on native. May keep <input type='file' accept='image/*' capture> as web fallback. Hook may live in a parent passed as props.",
|
|
304
|
+
"what_gap_looks_like": "<input type='file' accept='image/*' capture> used with no useCamera import in component tree. No isNative check. No camera native branch.",
|
|
305
|
+
"how_to_check": [
|
|
306
|
+
"signal old_pattern: files with accept=['\"]image.*capture or <input[^>]+capture",
|
|
307
|
+
"signal hook_usage: files importing useCamera from catalyst-core/hooks",
|
|
308
|
+
"llm judges: for each capture input file, does useCamera exist in it or a parent component?"
|
|
309
|
+
],
|
|
310
|
+
"fix_guide": "Replace <input type='file' accept='image/*' capture> with useCamera. import { useCamera } from 'catalyst-core/hooks'. const { isNative, takePhoto, data } = useCamera(); On native: <button onClick={takePhoto}>Take Photo</button>. Keep <input type='file' accept='image/*' capture> as web-only fallback: if (!isNative) return <input type='file' accept='image/*' capture onChange={...} />.",
|
|
311
|
+
"verify_hint": "On device/emulator, tap the camera button. System camera app should open. After taking a photo, the image data should be available in the component (log data to confirm). If camera opens but photo is not returned, check permissions — call requestPermission() before takePhoto() on first use.",
|
|
312
|
+
"depends_on": [
|
|
313
|
+
"T8_CLIENT_ENTRY"
|
|
314
|
+
]
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"id": "T18_USE_HAPTIC",
|
|
318
|
+
"tier": 3,
|
|
319
|
+
"title": "Vibration API replaced with useHapticFeedback",
|
|
320
|
+
"detect_pattern": "signal_collection + llm_review",
|
|
321
|
+
"review_question": "Are navigator.vibrate() calls unconverted haptic triggers that need useHapticFeedback, or are they in test files, comments, or polyfills that should be ignored?",
|
|
322
|
+
"what_correct_looks_like": "useHapticFeedback imported from catalyst-core/hooks. trigger() called with type string. navigator.vibrate only appears in web fallback polyfills (if at all).",
|
|
323
|
+
"what_gap_looks_like": "navigator.vibrate() called directly in component or utility code with no useHapticFeedback import.",
|
|
324
|
+
"how_to_check": [
|
|
325
|
+
"signal old_pattern: files with navigator\\.vibrate",
|
|
326
|
+
"signal hook_usage: files importing useHapticFeedback from catalyst-core/hooks",
|
|
327
|
+
"llm judges: is navigator.vibrate in active component code (gap) or in test/polyfill/comment (ignore)?"
|
|
328
|
+
],
|
|
329
|
+
"fix_guide": "Replace navigator.vibrate() with useHapticFeedback. import { useHapticFeedback } from 'catalyst-core/hooks'. const { trigger } = useHapticFeedback(); trigger('medium'). Types: 'light' | 'medium' | 'heavy' | 'success' | 'warning' | 'error'. No isNative guard needed — hook is a no-op on web.",
|
|
330
|
+
"verify_hint": "On a physical device (not emulator — emulators do not have haptic motors), trigger the action that previously called navigator.vibrate(). You should feel a vibration. No vibration on device means WebBridge is not initialized or the trigger type string is wrong. Emulator testing: check native logs for the haptic command being sent instead.",
|
|
331
|
+
"depends_on": [
|
|
332
|
+
"T8_CLIENT_ENTRY"
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"id": "T19_USE_NOTIFICATIONS",
|
|
337
|
+
"tier": 3,
|
|
338
|
+
"title": "Push notifications setup — useNotification + Firebase files",
|
|
339
|
+
"detect_pattern": "file_exists + field_check",
|
|
340
|
+
"how_to_check": [
|
|
341
|
+
"field: WEBVIEW_CONFIG.notifications.enabled must be true (optional — skip if notifications not needed)",
|
|
342
|
+
"file: google-services.json must exist at project root (if notifications enabled)",
|
|
343
|
+
"file: GoogleService-Info.plist must exist at project root (if notifications enabled)"
|
|
344
|
+
],
|
|
345
|
+
"fix_guide": "1) Add to WEBVIEW_CONFIG: notifications: { enabled: true }. 2) Download google-services.json from Firebase console (Android) and place at project root. 3) Download GoogleService-Info.plist from Firebase console (iOS) and place at project root. 4) Use hook: import { useNotification } from 'catalyst-core/hooks'. Missing Firebase files with notifications.enabled=true = build error.",
|
|
346
|
+
"verify_hint": "Build and install app. Grant notification permission when prompted. Send a test push from Firebase console (Cloud Messaging > Send test message). Notification should arrive in the device tray. If no permission prompt appears, useNotification hook is not calling requestPermission(). If build fails, google-services.json or GoogleService-Info.plist is missing or has wrong package name / bundle ID.",
|
|
347
|
+
"depends_on": [
|
|
348
|
+
"T9_WEBVIEW_ANDROID",
|
|
349
|
+
"T10_WEBVIEW_IOS"
|
|
350
|
+
]
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "T20_USE_DEVICE_INFO",
|
|
354
|
+
"tier": 3,
|
|
355
|
+
"title": "navigator.userAgent platform detection replaced with isNative / WebBridge",
|
|
356
|
+
"detect_pattern": "signal_collection + llm_review",
|
|
357
|
+
"review_question": "Are navigator.userAgent / UA-sniffing patterns used for platform detection in active component/utility code, or are they in test files, SSR utilities, analytics, or third-party code where UA sniffing is acceptable?",
|
|
358
|
+
"what_correct_looks_like": "Platform detection uses isNative from catalyst hooks (e.g. const { isNative } = useNetworkStatus()), or window.NativeBridge presence check for imperative code. For Android/iOS distinction: nativeBridge.isAndroid / nativeBridge.isIOS. navigator.userAgent only in analytics, third-party integrations, or server-side code.",
|
|
359
|
+
"what_gap_looks_like": "Component or utility code checks navigator.userAgent or runs /Android/i.test(navigator.userAgent) to branch UI/behavior. This is unreliable inside WebView.",
|
|
360
|
+
"how_to_check": [
|
|
361
|
+
"signal old_pattern: files with navigator\\.userAgent or /Android/i\\.test or /iPhone/i\\.test or /iPad/i\\.test",
|
|
362
|
+
"signal correct_pattern: files with isNative or window.NativeBridge (non-UA-sniffing platform checks)",
|
|
363
|
+
"llm judges: is the UA-sniffing in component/utility code for platform branching (gap) or in analytics/server/third-party (acceptable)?"
|
|
364
|
+
],
|
|
365
|
+
"fix_guide": "Replace navigator.userAgent platform detection with catalyst APIs. In React components: destructure isNative from any catalyst hook — e.g. const { isNative } = useNetworkStatus() — isNative is true inside the native WebView shell, false on web. For Android/iOS distinction in components: import { useBaseHook } from catalyst-core/hooks and use base.isAndroid / base.isIOS. For imperative code outside React: check window.NativeBridge presence (Android) or window.webkit?.messageHandlers?.NativeBridge (iOS). Do NOT use window.__PLATFORM__ — that API does not exist in catalyst-core.",
|
|
366
|
+
"verify_hint": "Open the app on Android device and on iOS device (or simulators). Any UI that branches by platform (Android-only buttons, iOS-only UI) should appear correctly on each. On web, isNative should be false — platform-specific UI should not render. Add a temporary console.log(isNative) to confirm the value is correct on each surface before removing it.",
|
|
367
|
+
"depends_on": [
|
|
368
|
+
"T8_CLIENT_ENTRY"
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
]
|