@corva/create-app 0.114.0 → 0.116.0

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.
@@ -187,7 +187,9 @@ const resolveDataForZipNodeJsApp = async (itemsToZip = [], { options, pkg, dirNa
187
187
 
188
188
  itemsToZip.push('config', 'manifest.json', 'package.json', '.nvmrc', '.npmrc', '.eslintrc.js');
189
189
 
190
- if (packageDirContent.includes('package-lock.json')) {
190
+ if (packageDirContent.includes('pnpm-lock.yaml')) {
191
+ itemsToZip.push('pnpm-lock.yaml');
192
+ } else if (packageDirContent.includes('package-lock.json')) {
191
193
  itemsToZip.push('package-lock.json');
192
194
 
193
195
  if (shouldUpdateVersion) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corva/create-app",
3
- "version": "0.114.0",
3
+ "version": "0.116.0",
4
4
  "private": false,
5
5
  "description": "Create an app to use it in CORVA.AI",
6
6
  "keywords": [
@@ -0,0 +1,3 @@
1
+ [mcp_servers.corva-ui]
2
+ command = "npx"
3
+ args = ["-p", "@corva/ui", "corva-ui-mcp"]
@@ -0,0 +1,301 @@
1
+ # AGENTS.md
2
+
3
+ Corva.AI platform UI app — a React dashboard widget scaffolded by `create-corva-app`.
4
+ Deployed to the Corva platform via `yarn release`.
5
+
6
+ ## Critical Rules
7
+
8
+ These constraints are enforced by the platform. Violating them breaks the build or runtime.
9
+
10
+ 1. **Root export** — `src/index.js` MUST default-export `{ component: App, settings: AppSettings }`. The component may also be a `ParentApp` wrapper (e.g., `{ component: ParentApp, settings: AppSettings }`) that wraps `App` with Context.Providers and prop initialization. This is the platform loader contract.
11
+ 2. **UI components** — Use only `@corva/ui` components. Import from `@corva/ui/componentsV2`; fall back to `@corva/ui/components` only when a V2 version doesn't exist yet. `@material-ui/core` v4 is available as the underlying library.
12
+ 3. **HTTP clients** — Use `corvaDataAPI` / `corvaAPI` from `@corva/ui/clients` for all network requests.
13
+ 4. **Do not rename or delete** `App.js`, `AppSettings.js`, or `index.js` — they are platform entry points.
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ src/
19
+ App.js — Main app component (default export required)
20
+ AppSettings.js — Settings panel component (default export required)
21
+ index.js — Root export: { component, settings }
22
+ constants.js — Default settings values
23
+ App.css — Component styles (CSS modules)
24
+ __tests__/ — Jest test files
25
+ __mocks__/ — Mock data (mockAppProps.js, mockAppSettingsProps.js)
26
+ assets/ — Static assets (SVGs, images)
27
+ config/jest/ — Jest setup (setupTests, transforms)
28
+ config-overrides.js — Webpack 5 customization
29
+ manifest.json — Corva platform app metadata (generated at scaffold time)
30
+ ```
31
+
32
+ ## Key Patterns
33
+
34
+ ### Component Structure
35
+
36
+ - Use JSDoc `@param` / `@returns` annotations for prop documentation.
37
+ - Use `useAppCommons()` from `@corva/ui/effects` for app context (`appKey`).
38
+
39
+ ### Styling
40
+
41
+ CSS modules for component styles:
42
+
43
+ ```javascript
44
+ import styles from './App.css';
45
+ // Use: <div className={styles.container}>
46
+ ```
47
+
48
+ For custom component styles, use `makeStyles` from MUI v4:
49
+
50
+ ```javascript
51
+ import { makeStyles } from '@material-ui/core/styles';
52
+
53
+ const useStyles = makeStyles(theme => ({
54
+ root: { display: 'flex', gap: theme.spacing(1) },
55
+ }));
56
+
57
+ // Inside component:
58
+ const styles = useStyles();
59
+ return <div className={styles.root}>...</div>;
60
+ ```
61
+
62
+ **`theme.spacing(n)`** — 8px base unit for layout dimensions:
63
+
64
+ ```javascript
65
+ padding: theme.spacing(2); // 16px
66
+ margin: theme.spacing(1, 0); // 8px 0
67
+ borderRadius: theme.spacing(0.5); // 4px
68
+ ```
69
+
70
+ **Color manipulation** — `fade`, `darken`, `lighten` from `@material-ui/core/styles`:
71
+
72
+ ```javascript
73
+ import { fade, darken, lighten } from '@material-ui/core/styles';
74
+
75
+ background: fade(theme.palette.common.white, 0.08); // white at 8% opacity
76
+ color: darken(theme.palette.primary.main, 0.2); // darken by 20%
77
+ ```
78
+
79
+ **Transitions:**
80
+
81
+ ```javascript
82
+ transition: theme.transitions.create('opacity');
83
+ transition: theme.transitions.create(['opacity', 'background-color'], {
84
+ duration: theme.transitions.duration.standard, // 300ms
85
+ });
86
+ ```
87
+
88
+ **Breakpoints:**
89
+
90
+ ```javascript
91
+ [theme.breakpoints.down('sm')]: { padding: theme.spacing(1) } // max-width: 599px
92
+ [theme.breakpoints.up('md')]: { flexDirection: 'row' } // min-width: 960px
93
+ ```
94
+
95
+ **`theme.zIndex`** — stacking constants: `appBar` (1100), `drawer` (1200), `modal` (1300), `tooltip` (1500).
96
+
97
+ **CSS variables** — available in `.css` files:
98
+
99
+ ```css
100
+ color: var(--palette-primary-text-1); /* primary text */
101
+ background: var(--palette-background-b-5); /* card background */
102
+ border-color: var(--palette-primary-text-9); /* borders */
103
+ ```
104
+
105
+ **SCSS functions** — available in `.module.scss` files:
106
+
107
+ ```scss
108
+ padding: spacing(2); // 16px
109
+ color: colorAlpha($color, 0.5); // rgba transparency
110
+ transition: transition(opacity); // standard cubic-bezier
111
+ ```
112
+
113
+ **Direct theme import:**
114
+
115
+ ```javascript
116
+ import { lightTheme, darkTheme } from '@corva/ui/config';
117
+ ```
118
+
119
+ ### State Management (Zustand)
120
+
121
+ The Corva platform can render multiple instances of the same app simultaneously. If a Zustand store is created at module level (singleton), all instances share the same state. To avoid this, always create store instances inside a React Context provider.
122
+
123
+ **Store factory + context** — create a factory function and a context to hold the store instance:
124
+
125
+ ```javascript
126
+ // store/createAppStore.js
127
+ import { createContext } from 'react';
128
+ import { create } from 'zustand';
129
+
130
+ export const createAppStore = (initProps = {}) => {
131
+ return create((set, get) => ({
132
+ // Compose sub-stores
133
+ ...createSettingsStore(initProps.settings)(set, get),
134
+ ...createDataStore()(set, get),
135
+ }));
136
+ };
137
+
138
+ export const StoreContext = createContext(null);
139
+ ```
140
+
141
+ **Provider** — create the store once per app mount and pass it via context:
142
+
143
+ ```javascript
144
+ // StoreProvider.js
145
+ import { useState } from 'react';
146
+
147
+ import { StoreContext, createAppStore } from './store/createAppStore';
148
+
149
+ export const StoreProvider = ({ children, savedSettings }) => {
150
+ const [appStore] = useState(() => createAppStore(savedSettings));
151
+
152
+ return <StoreContext.Provider value={appStore}>{children}</StoreContext.Provider>;
153
+ };
154
+ ```
155
+
156
+ **Consumer hooks** — read from context, never from a global store:
157
+
158
+ ```javascript
159
+ // hooks/useAppStore.js
160
+ import { useContext } from 'react';
161
+ import { useStore } from 'zustand';
162
+ import { useStoreWithEqualityFn } from 'zustand/traditional';
163
+
164
+ import { StoreContext } from '../store/createAppStore';
165
+
166
+ /** Read a single top-level key from the store. */
167
+ export const useAppStore = key => {
168
+ const store = useContext(StoreContext);
169
+ if (!store) throw new Error('useAppStore must be used within StoreProvider');
170
+ return useStore(store, state => state[key]);
171
+ };
172
+
173
+ /** Subscribe to a derived value with optional custom equality. */
174
+ export const useAppStoreSelector = (selector, equalityFn) => {
175
+ const store = useContext(StoreContext);
176
+ if (!store) throw new Error('useAppStoreSelector must be used within StoreProvider');
177
+ return useStoreWithEqualityFn(store, selector, equalityFn);
178
+ };
179
+ ```
180
+
181
+ **Usage in components:**
182
+
183
+ ```javascript
184
+ const isLegendVisible = useAppStore('isLegendVisible');
185
+ const selectedChannels = useAppStoreSelector(state => state.selectedChannels);
186
+ ```
187
+
188
+ **Wire it up in `ParentApp`** (see full example with `QueryClientProvider` in the Data Fetching section below).
189
+
190
+ General rules:
191
+
192
+ - Name stores `use[Name]Store` (e.g., `useWellDataStore`)
193
+ - Use selectors: `const value = useAppStore('value')`
194
+ - Keep business logic in store actions
195
+
196
+ ### Data Fetching (React Query v4)
197
+
198
+ Same as Zustand: the platform runs multiple app instances, so each must have its own `QueryClient`. Never create a `QueryClient` at module level — use a hook with `useState` to create it once per mount.
199
+
200
+ **`useQueryClient` hook** — creates an isolated `QueryClient` per app instance:
201
+
202
+ ```javascript
203
+ // hooks/useQueryClient.js
204
+ import { useState } from 'react';
205
+ import { QueryCache, QueryClient } from '@tanstack/react-query';
206
+ import { showErrorNotification } from '@corva/ui/utils';
207
+
208
+ export const useQueryClient = () => {
209
+ const [queryClient] = useState(
210
+ new QueryClient({
211
+ queryCache: new QueryCache({
212
+ onError: error => {
213
+ if (error?.message) {
214
+ showErrorNotification(`Something went wrong: ${error.message}`);
215
+ }
216
+ },
217
+ }),
218
+ defaultOptions: {
219
+ queries: {
220
+ refetchOnWindowFocus: false,
221
+ refetchOnReconnect: false,
222
+ },
223
+ },
224
+ })
225
+ );
226
+
227
+ return queryClient;
228
+ };
229
+ ```
230
+
231
+ **Wire it up in `ParentApp`** — wrap the app tree with `QueryClientProvider`:
232
+
233
+ ```javascript
234
+ import { QueryClientProvider } from '@tanstack/react-query';
235
+
236
+ const ParentApp = () => {
237
+ const { appSettings, onSettingsChange } = useAppCommons();
238
+ const queryClient = useQueryClient();
239
+
240
+ return (
241
+ <QueryClientProvider client={queryClient}>
242
+ <StoreProvider savedSettings={appSettings?.savedSettings}>
243
+ <App />
244
+ </StoreProvider>
245
+ </QueryClientProvider>
246
+ );
247
+ };
248
+
249
+ export default { component: ParentApp, settings: AppSettings };
250
+ ```
251
+
252
+ General rules:
253
+
254
+ - Encapsulate queries in custom hooks (e.g., `useWellData`)
255
+ - Use typed query keys (array format)
256
+ - Always handle `isLoading` and `isError` states
257
+
258
+ ## Naming Conventions
259
+
260
+ - Boolean variables: prefix with `is`, `has`, or `should`
261
+ - Non-empty array checks: `!!array.length` (not `array.length > 0`)
262
+
263
+ ## Testing
264
+
265
+ - **Framework:** Jest + React Testing Library
266
+ - **Wrapper:** Use `AppTestWrapper` from `@corva/ui/testing` to wrap components
267
+ - **Mocking:** Mock network requests (jest mocks or msw), never call real APIs
268
+ - **Timezone:** UTC is enforced (`process.env.TZ = 'UTC'`)
269
+ - **Mocked globals:** `ResizeObserver`, `MutationObserver` (in `setupTests.js`)
270
+
271
+ ## Commands
272
+
273
+ ```
274
+ yarn start Dev server at http://app.local.corva.ai:8080/
275
+ yarn build Production bundle
276
+ yarn test Run tests
277
+ yarn lint ESLint check
278
+ yarn zip Create deployment ZIP
279
+ yarn release Release to Corva platform
280
+ ```
281
+
282
+ ## @corva/ui Documentation
283
+
284
+ Local documentation for `@corva/ui` is bundled at `node_modules/@corva/ui/docs/`.
285
+ Before implementing any UI component, data fetch, or API call, read the relevant doc file:
286
+
287
+ - **V2 Components:** `node_modules/@corva/ui/docs/01-components-v2/` — Props, examples, usage
288
+ - **V1 Components:** `node_modules/@corva/ui/docs/02-components-v1/` — Legacy components reference
289
+ - **Hooks:** `node_modules/@corva/ui/docs/03-hooks/` — Parameters, return types, examples
290
+ - **API Clients:** `node_modules/@corva/ui/docs/04-clients/` — corvaAPI, corvaDataAPI endpoints
291
+ - **Theme:** `node_modules/@corva/ui/docs/05-theme/` — Color palette, CSS variables
292
+ - **Utilities:** `node_modules/@corva/ui/docs/06-utilities/` — Helper functions
293
+ - **Constants:** `node_modules/@corva/ui/docs/07-constants/` — Platform constants
294
+
295
+ Start with `node_modules/@corva/ui/docs/README.md` for the full index.
296
+ Do NOT guess `@corva/ui` APIs — read the local docs first.
297
+
298
+ ## MCP Server (Interactive Fallback)
299
+
300
+ The `corva-ui` MCP server is pre-configured (`.mcp.json`, `.cursor/mcp.json`, `.codex/config.toml`).
301
+ Use it for interactive queries when the local docs are insufficient or you need to search across components.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -6,21 +6,10 @@ import logo from './assets/logo.svg';
6
6
 
7
7
  import styles from './App.css';
8
8
 
9
- /**
10
- * @param {Object} props
11
- * @param {boolean} props.isExampleCheckboxChecked
12
- * @param {Object} props.fracFleet
13
- * @param {Object} props.well
14
- * @param {Object[]} props.wells
15
- * @returns
16
- */
17
- function App({
18
- isExampleCheckboxChecked = DEFAULT_SETTINGS.isExampleCheckboxChecked,
19
- fracFleet,
20
- well,
21
- wells,
22
- }) {
23
- const { appKey } = useAppCommons();
9
+ const App = () => {
10
+ const { appKey, fracFleet, well, wells, appSettings } = useAppCommons();
11
+ const { isExampleCheckboxChecked = DEFAULT_SETTINGS.isExampleCheckboxChecked } =
12
+ appSettings || {};
24
13
  // NOTE: On general type dashboard app receives wells array
25
14
  // on asset type dashboard app receives well object
26
15
  const wellsList = wells || [well];
@@ -56,7 +45,7 @@ function App({
56
45
  </div>
57
46
  </AppContainer>
58
47
  );
59
- }
48
+ };
60
49
 
61
50
  // Important: Do not change root component default export (App.js). Use it as container
62
51
  // for your App. It's required to make build and zip scripts work as expected;
@@ -6,15 +6,11 @@ import logo from './assets/logo.svg';
6
6
 
7
7
  import styles from './App.css';
8
8
 
9
- /**
10
- * @param {Object} props
11
- * @param {boolean} props.isExampleCheckboxChecked
12
- * @param {Object} props.rig
13
- * @param {Object} props.well
14
- * @returns
15
- */
16
- function App({ isExampleCheckboxChecked = DEFAULT_SETTINGS.isExampleCheckboxChecked, rig, well }) {
17
- const { appKey } = useAppCommons();
9
+ const App = () => {
10
+ const { appKey, rig, well, appSettings } = useAppCommons();
11
+ const { isExampleCheckboxChecked = DEFAULT_SETTINGS.isExampleCheckboxChecked } =
12
+ appSettings || {};
13
+
18
14
  return (
19
15
  <AppContainer header={<AppHeader />} testId={appKey}>
20
16
  <div className={styles.container}>
@@ -46,7 +42,7 @@ function App({ isExampleCheckboxChecked = DEFAULT_SETTINGS.isExampleCheckboxChec
46
42
  </div>
47
43
  </AppContainer>
48
44
  );
49
- }
45
+ };
50
46
 
51
47
  // Important: Do not change root component default export (App.js). Use it as container
52
48
  // for your App. It's required to make build and zip scripts work as expected;
@@ -1,26 +1,12 @@
1
1
  import { Checkbox, FormControlLabel } from '@material-ui/core';
2
+ import { useAppCommons } from '@corva/ui/effects';
2
3
 
3
4
  import { DEFAULT_SETTINGS } from './constants';
4
5
 
5
- /**
6
- * @param {Object} props
7
- * @param {Object} props.app
8
- * @param {Object} props.appData
9
- * @param {Object} props.company
10
- * @param {(key: string, value: any) => void} props.onSettingChange
11
- * @param {{isExampleCheckboxChecked: boolean}} props.settings
12
- * @param {Object} props.user
13
- * @returns
14
- */
15
- function AppSettings({
16
- settings: apiSettings,
17
- onSettingChange,
18
- // appData,
19
- // app,
20
- // user = {},
21
- // company = {},
22
- }) {
23
- const settings = { ...DEFAULT_SETTINGS, ...apiSettings };
6
+ const AppSettings = () => {
7
+ const { appSettings, onSettingChange } = useAppCommons();
8
+ const settings = { ...DEFAULT_SETTINGS, ...appSettings };
9
+
24
10
  return (
25
11
  <div>
26
12
  <FormControlLabel
@@ -35,7 +21,7 @@ function AppSettings({
35
21
  />
36
22
  </div>
37
23
  );
38
- }
24
+ };
39
25
 
40
26
  // Important: Do not change root component default export (AppSettings.js). Use it as container
41
27
  // for your App Settings. It's required to make build and zip scripts work as expected;
@@ -2,21 +2,12 @@ import { render, screen } from '@testing-library/react';
2
2
  import { AppTestWrapper } from '@corva/ui/testing';
3
3
 
4
4
  import App from '../App';
5
- import { mockAppProps } from '../__mocks__/mockAppProps';
6
5
 
7
6
  describe('<App />', () => {
8
7
  it('should show correct layout', () => {
9
8
  render(
10
- <AppTestWrapper
11
- app={mockAppProps.app}
12
- appId={123}
13
- maximized={false}
14
- appSettings={mockAppProps.app.settings}
15
- onSettingChange={() => {
16
- /* noop */
17
- }}
18
- >
19
- <App {...mockAppProps} />
9
+ <AppTestWrapper appSettings={{ isExampleCheckboxChecked: true }}>
10
+ <App />
20
11
  </AppTestWrapper>
21
12
  );
22
13
 
@@ -24,20 +15,9 @@ describe('<App />', () => {
24
15
  });
25
16
 
26
17
  it('should show correct layout when settings are not provided', () => {
27
- const propsWithoutSettings = mockAppProps;
28
- delete propsWithoutSettings.isExampleCheckboxChecked;
29
-
30
18
  render(
31
- <AppTestWrapper
32
- app={mockAppProps.app}
33
- appId={123}
34
- maximized={false}
35
- appSettings={mockAppProps.app.settings}
36
- onSettingChange={() => {
37
- /* noop */
38
- }}
39
- >
40
- <App {...propsWithoutSettings} />
19
+ <AppTestWrapper appSettings={{}}>
20
+ <App />
41
21
  </AppTestWrapper>
42
22
  );
43
23
 
@@ -1,21 +1,28 @@
1
1
  import { render, screen, act } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
+ import { AppTestWrapper } from '@corva/ui/testing';
3
4
 
4
5
  import AppSettings from '../AppSettings';
5
- import { mockAppSettingsProps } from '../__mocks__/mockAppSettingsProps';
6
6
 
7
7
  describe('<AppSettings />', () => {
8
8
  it('should call onChange with a changed setting on settings change', async () => {
9
- const handleSettingsChange = jest.fn();
9
+ const handleSettingChange = jest.fn();
10
10
 
11
- render(<AppSettings {...mockAppSettingsProps} onSettingChange={handleSettingsChange} />);
11
+ render(
12
+ <AppTestWrapper
13
+ appSettings={{ isExampleCheckboxChecked: true }}
14
+ onSettingChange={handleSettingChange}
15
+ >
16
+ <AppSettings />
17
+ </AppTestWrapper>
18
+ );
12
19
 
13
- const exampleCheckbox = screen.getByTestId('exampleCheckbox').querySelector('input');
20
+ const exampleCheckbox = screen.getByRole('checkbox', { name: /example/i });
14
21
 
15
22
  await act(async () => {
16
23
  await userEvent.click(exampleCheckbox);
17
24
  });
18
25
 
19
- expect(handleSettingsChange).toBeCalledWith('isExampleCheckboxChecked', false);
26
+ expect(handleSettingChange).toBeCalledWith('isExampleCheckboxChecked', false);
20
27
  });
21
28
  });
@@ -1,3 +1,4 @@
1
+ // DO NOT modify this structure
1
2
  import App from './App';
2
3
  import AppSettings from './AppSettings';
3
4
 
@@ -0,0 +1,3 @@
1
+ [mcp_servers.corva-ui]
2
+ command = "npx"
3
+ args = ["-p", "@corva/ui", "corva-ui-mcp"]