@corva/create-app 0.117.0 → 0.118.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.
@@ -27,12 +27,15 @@ const jsUiDevDependencies = {
27
27
  '@testing-library/react': '^12.1.5',
28
28
  '@testing-library/react-hooks': '^8.0.1',
29
29
  '@testing-library/user-event': '^13.2.1',
30
- 'jest-watch-typeahead': '^1.0.0',
31
- 'jest': '^27.4.3',
32
30
  'babel-jest': '^27.4.2',
33
31
  'babel-preset-react-app': '^10.0.1',
34
32
  'eslint': '7.32.0',
33
+ 'identity-obj-proxy': '^3.0.0',
34
+ 'jest': '^27.4.3',
35
+ 'jest-watch-typeahead': '^1.0.0',
35
36
  'postcss-loader': '4.1.0',
37
+ 'sass': '^1.89.2',
38
+ 'sass-loader': '^16.0.5',
36
39
  };
37
40
 
38
41
  const tsUiDevDependencies = {
@@ -124,12 +127,12 @@ const uiPackage = {
124
127
  },
125
128
  transformIgnorePatterns: [
126
129
  '/node_modules/(?!.*@babel/runtime|@icon-park/react/es).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
127
- '^.+\\.module\\.(css|sass|scss)$',
130
+ '^.+\\.(css|sass|scss)$',
128
131
  ],
129
132
  modulePaths: [],
130
133
  moduleNameMapper: {
131
134
  '~(.*)': '<rootDir>/src/$1',
132
- '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
135
+ '^.+\\.(css|sass|scss)$': 'identity-obj-proxy',
133
136
  '@corva/ui(.*)': '@corva/ui/cjs-bundle/$1',
134
137
  },
135
138
  watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
@@ -162,16 +165,19 @@ const nodeNpmScripts = {
162
165
  };
163
166
 
164
167
  const nodeDependencies = {
165
- '@corva/node-sdk': '^8.3.0',
168
+ '@corva/node-sdk': '^8.5.0',
166
169
  };
167
170
 
168
171
  const nodeDevDependencies = {
169
- '@corva/eslint-config-node': '^5.1.1',
172
+ '@corva/eslint-config-node': '^6.0.0',
173
+ '@typescript-eslint/eslint-plugin': '^8.58.1',
174
+ '@typescript-eslint/parser': '^8.58.1',
170
175
  'jest': '^29.7.0',
171
176
  'dotenv': '^16.0.3',
172
177
  'eslint': '^8.2.0',
178
+ 'eslint-plugin-jest': '^27.1.5',
173
179
  'prettier': '^2.0.0',
174
- 'typescript': '^5.5.4',
180
+ 'typescript': '^5.9.3',
175
181
  };
176
182
 
177
183
  const nodeTsDevDependencies = {
@@ -216,6 +222,36 @@ const nodeNpmPackage = {
216
222
  eslintConfig: {
217
223
  root: true,
218
224
  extends: '@corva/eslint-config-node',
225
+ overrides: [
226
+ {
227
+ files: ['**/*.ts'],
228
+ parser: '@typescript-eslint/parser',
229
+ parserOptions: {
230
+ project: 'tsconfig.json',
231
+ sourceType: 'module',
232
+ },
233
+ plugins: ['@typescript-eslint/eslint-plugin'],
234
+ rules: {
235
+ '@typescript-eslint/padding-line-between-statements': 'off',
236
+ },
237
+ },
238
+ {
239
+ files: [
240
+ '**/*.spec.js',
241
+ '**/*.spec.ts',
242
+ '**/*.spec.mjs',
243
+ '**/*.spec.cjs',
244
+ '**/__mocks__/**/*.js',
245
+ '**/__mocks__/**/*.ts',
246
+ '**/__mocks__/**/*.cjs',
247
+ '**/__mocks__/**/*.mjs',
248
+ ],
249
+ plugins: ['jest'],
250
+ env: {
251
+ jest: true,
252
+ },
253
+ },
254
+ ],
219
255
  },
220
256
  };
221
257
 
@@ -242,7 +278,8 @@ const nodeTsYarnPackage = {
242
278
 
243
279
  const extendWithTsConfig = (packageJson, version) => {
244
280
  packageJson.devDependencies = {
245
- [`@tsconfig/node${version}`]: `^${version === '12' ? '1.0.9' : '1.0.1'}`,
281
+ // New @tsconfig/nodeX package versioning: it starts with X major version
282
+ [`@tsconfig/node${version}`]: `^${version}`,
246
283
  ...packageJson.devDependencies,
247
284
  };
248
285
 
@@ -11,7 +11,7 @@ const SCHEDULER_MAPPING = [SCHEDULER_TYPE_DATA_TIME, SCHEDULER_TYPE_DEPTH, SCHED
11
11
  {},
12
12
  );
13
13
 
14
- const NODE_RUNTIMES = [APP_RUNTIMES.NODE18, APP_RUNTIMES.NODE20, APP_RUNTIMES.NODE22, APP_RUNTIMES.NODE24];
14
+ const NODE_RUNTIMES = [APP_RUNTIMES.NODE24];
15
15
 
16
16
  export class Manifest {
17
17
  constructor(manifest) {
@@ -255,7 +255,7 @@ const resolveDataForZipPythonApp = async (itemsToZip = [], { manifest, dirName,
255
255
  );
256
256
  }
257
257
 
258
- itemsToZip.push('manifest.json', 'requirements.txt');
258
+ itemsToZip.push('manifest.json', 'pyproject.toml', 'uv.lock', 'requirements.txt');
259
259
 
260
260
  return {
261
261
  zipFileName,
@@ -6,6 +6,8 @@ import os from 'os';
6
6
  import semver from 'semver';
7
7
 
8
8
  const debug = debugFn('cca:resolve-app-runtime');
9
+ const DEFAULT_UI_NODE_VERSION = '24';
10
+ const DEFAULT_UI_NODE_RANGE = '>=24 <25';
9
11
 
10
12
  /**
11
13
  *
@@ -41,7 +43,7 @@ const checkCliVersion = async (command, version) =>
41
43
  });
42
44
  });
43
45
 
44
- const checkNodeVersion = (version) => async () => {
46
+ const checkNodeVersion = (range) => async () => {
45
47
  try {
46
48
  await fs.access(`${os.homedir()}/.nvm/nvm.sh`);
47
49
 
@@ -52,7 +54,7 @@ const checkNodeVersion = (version) => async () => {
52
54
  debug(e);
53
55
  debug('nvm is not installed, checking node version');
54
56
 
55
- return checkCliVersion('node', version);
57
+ return checkCliVersion('node', range);
56
58
  }
57
59
  };
58
60
 
@@ -60,19 +62,22 @@ export const IS_WINDOWS = process.platform === 'win32';
60
62
 
61
63
  const semverVersionsMapping = {
62
64
  node: {
63
- 18: '18.14.0',
64
- 20: '20.16.0',
65
+ 18: '18.20.8',
66
+ 20: '20.20.1',
65
67
  24: '24.14.0',
66
68
  },
67
69
  python: {
68
- '3.8': '3.8.16',
69
- '3.9': '3.9.16',
70
- '3.10': '3.10.9',
71
- '3.11': '3.11.1',
72
- '3.13': '3.13.7',
70
+ 3.13: '3.13.13',
71
+ 3.14: '3.14.4',
73
72
  },
74
73
  };
75
74
 
75
+ const nodeVersionRanges = {
76
+ 18: '>=18 <19',
77
+ 20: '>=20 <21',
78
+ 24: '>=24 <25',
79
+ };
80
+
76
81
  /**
77
82
  * @typedef {Object} Runtime
78
83
  * @property {string} language
@@ -80,17 +85,20 @@ const semverVersionsMapping = {
80
85
  * @property {string} packageManager
81
86
  * @property {string} version
82
87
  * @property {string} semver
88
+ * @property {string} [enginesNode]
83
89
  */
84
90
  export const resolveAppRuntime = (opts) => {
85
91
  if (opts.appType === APP_TYPES.UI) {
86
- const version = '20';
92
+ const version = DEFAULT_UI_NODE_VERSION;
87
93
 
88
94
  return {
95
+ environment: 'node',
89
96
  language: opts.useTypescript ? 'typescript' : 'javascript',
90
- isRuntimeAvailable: checkNodeVersion(version),
97
+ isRuntimeAvailable: checkNodeVersion(DEFAULT_UI_NODE_RANGE),
91
98
  packageManager: opts.packageManager,
92
99
  version,
93
100
  semver: semverVersionsMapping.node[version],
101
+ enginesNode: DEFAULT_UI_NODE_RANGE,
94
102
  };
95
103
  }
96
104
 
@@ -98,17 +106,20 @@ export const resolveAppRuntime = (opts) => {
98
106
  const version = /nodejs(\d{2})\.x/.exec(opts.runtime)[1];
99
107
 
100
108
  return {
109
+ environment: 'node',
101
110
  language: opts.useTypescript ? 'typescript' : 'javascript',
102
- isRuntimeAvailable: checkNodeVersion(version),
111
+ isRuntimeAvailable: checkNodeVersion(nodeVersionRanges[version]),
103
112
  packageManager: opts.packageManager,
104
113
  version,
105
114
  semver: semverVersionsMapping.node[version],
115
+ enginesNode: `^${version}`,
106
116
  };
107
117
  }
108
118
 
109
119
  const version = /python(\d\.\d+)/.exec(opts.runtime)[1];
110
120
 
111
121
  return {
122
+ environment: 'python',
112
123
  language: 'python',
113
124
  isRuntimeAvailable: async () => {
114
125
  if (!IS_WINDOWS) {
@@ -26,9 +26,13 @@ export const addUiAppFile = (templateFolder, root, runtime, manifest, opts) => {
26
26
  };
27
27
 
28
28
  export const getExcludedFiles = (manifest) => {
29
- if (manifest.isUi()) return ['App.drilling.js', 'App.completion.js', 'App.drilling.tsx', 'App.completion.tsx'];
29
+ const excludedFiles = ['.ruff_cache', '.venv', '.pytest_cache', '__pycache__'];
30
30
 
31
- return [];
31
+ if (manifest.isUi()) {
32
+ return [...excludedFiles, 'App.drilling.js', 'App.completion.js', 'App.drilling.tsx', 'App.completion.tsx'];
33
+ }
34
+
35
+ return excludedFiles;
32
36
  };
33
37
 
34
38
  export function copyFileSync(source, target) {
package/lib/main.js CHANGED
@@ -17,17 +17,16 @@ import { StepError } from './flows/lib/step-error.js';
17
17
  function checkNodeVersion() {
18
18
  logger.write('Checking node version...');
19
19
 
20
- const unsupportedNodeVersion = !semver.satisfies(process.version, '>=16');
20
+ const unsupportedNodeVersion = !semver.satisfies(process.version, '>=24 <25');
21
21
 
22
22
  if (unsupportedNodeVersion) {
23
23
  logger.log(
24
24
  chalk.red(
25
25
  `\nYou are using Node ${process.version}.\n\n` +
26
- `Please update to Node 16 or higher for a better, fully supported experience.\n`,
26
+ `Please update to Node 24.x for a better, fully supported experience.\n`,
27
27
  ),
28
28
  );
29
29
 
30
- // Fall back to latest supported react-scripts on Node 4
31
30
  return process.exit(1);
32
31
  }
33
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corva/create-app",
3
- "version": "0.117.0",
3
+ "version": "0.118.0",
4
4
  "private": false,
5
5
  "description": "Create an app to use it in CORVA.AI",
6
6
  "keywords": [
@@ -70,18 +70,18 @@
70
70
  "@commitlint/cli": "^17.3.0",
71
71
  "@commitlint/config-conventional": "^17.3.0",
72
72
  "@corva/eslint-config-browser": "^0.1.7",
73
- "@corva/eslint-config-node": "^5.1.1",
74
- "@corva/node-sdk": "^8.0.1",
73
+ "@corva/eslint-config-node": "^6.0.0",
74
+ "@corva/node-sdk": "^8.5.0",
75
75
  "@types/cross-spawn": "^6.0.2",
76
76
  "@types/jest": "^29.5.4",
77
- "@typescript-eslint/eslint-plugin": "^5.42.1",
78
- "@typescript-eslint/parser": "^5.42.1",
77
+ "@typescript-eslint/eslint-plugin": "^8.58.1",
78
+ "@typescript-eslint/parser": "^8.58.1",
79
79
  "conventional-changelog-cli": "^2.1.0",
80
80
  "eslint": "^8.2.0",
81
+ "eslint-plugin-jest": "^27.1.5",
81
82
  "eslint-config-google": "^0.14.0",
82
83
  "eslint-config-prettier": "^8.5.0",
83
84
  "eslint-plugin-import": "^2.26.0",
84
- "eslint-plugin-jest": "^27.1.5",
85
85
  "eslint-plugin-jsx-a11y": "^6.7.1",
86
86
  "eslint-plugin-prettier": "^4.2.1",
87
87
  "eslint-plugin-react": "^7.32.0",
@@ -92,12 +92,12 @@
92
92
  "prettier": "^2.0.0",
93
93
  "prettier-plugin-packagejson": "^2.3.0",
94
94
  "standard-version": "^9.0.0",
95
- "typescript": "^4.8.4",
95
+ "typescript": "^5.9.3",
96
96
  "zx": "^7.2.3"
97
97
  },
98
98
  "packageManager": "yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71",
99
99
  "engines": {
100
- "node": ">=18"
100
+ "node": ">=24 <25"
101
101
  },
102
102
  "standard-version": {
103
103
  "skip": {
@@ -20,7 +20,7 @@ src/
20
20
  AppSettings.js — Settings panel component (default export required)
21
21
  index.js — Root export: { component, settings }
22
22
  constants.js — Default settings values
23
- App.css — Component styles (CSS modules)
23
+ App.scss — Component styles
24
24
  __tests__/ — Jest test files
25
25
  __mocks__/ — Mock data (mockAppProps.js, mockAppSettingsProps.js)
26
26
  assets/ — Static assets (SVGs, images)
@@ -38,83 +38,86 @@ manifest.json — Corva platform app metadata (generated at scaffold ti
38
38
 
39
39
  ### Styling
40
40
 
41
- CSS modules for component styles:
41
+ The preferred way to write styles is SCSS (`.scss` files).
42
42
 
43
- ```javascript
44
- import styles from './App.css';
45
- // Use: <div className={styles.container}>
43
+ Every `.scss` file must import the shared utilities:
44
+
45
+ ```scss
46
+ @import '@corva/ui/styles/common';
46
47
  ```
47
48
 
48
- For custom component styles, use `makeStyles` from MUI v4:
49
+ This provides all functions, variables, and mixins below.
49
50
 
50
- ```javascript
51
- import { makeStyles } from '@material-ui/core/styles';
51
+ For custom shared variables or mixins specific to the project, create a `src/styles/` directory (e.g. `_variables.scss`, `_common.scss`) and import them alongside the `@corva/ui` import.
52
52
 
53
- const useStyles = makeStyles(theme => ({
54
- root: { display: 'flex', gap: theme.spacing(1) },
55
- }));
53
+ **`spacing($top, $right?, $bottom?, $left?)`** 8px base unit:
56
54
 
57
- // Inside component:
58
- const styles = useStyles();
59
- return <div className={styles.root}>...</div>;
55
+ ```scss
56
+ padding: spacing(2); // 16px
57
+ margin: spacing(1, 0); // 8px 0
58
+ gap: spacing(0.5); // 4px
59
+ padding: spacing(2, 1, 2, 1); // 16px 8px 16px 8px
60
60
  ```
61
61
 
62
- **`theme.spacing(n)`** — 8px base unit for layout dimensions:
62
+ **`colorAlpha($color, $opacity)`** — transparency:
63
63
 
64
- ```javascript
65
- padding: theme.spacing(2); // 16px
66
- margin: theme.spacing(1, 0); // 8px 0
67
- borderRadius: theme.spacing(0.5); // 4px
64
+ ```scss
65
+ background: colorAlpha($palette_t1, 0.08); // white at 8% opacity
66
+ border: 1px solid colorAlpha($palette_t1, 0.12);
68
67
  ```
69
68
 
70
- **Color manipulation** `fade`, `darken`, `lighten` from `@material-ui/core/styles`:
69
+ **`transition($properties...)`**standard `cubic-bezier(0.4, 0, 0.2, 1) 0.15s`:
71
70
 
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%
71
+ ```scss
72
+ transition: transition(opacity);
73
+ transition: transition(color, background-color);
77
74
  ```
78
75
 
79
- **Transitions:**
76
+ **Full example:**
80
77
 
81
- ```javascript
82
- transition: theme.transitions.create('opacity');
83
- transition: theme.transitions.create(['opacity', 'background-color'], {
84
- duration: theme.transitions.duration.standard, // 300ms
85
- });
78
+ ```scss
79
+ @import '@corva/ui/styles/common';
80
+
81
+ .container {
82
+ padding: spacing(2);
83
+ background: $palette_b5;
84
+ color: $palette_t1;
85
+ transition: transition(opacity, background-color);
86
+
87
+ &:hover {
88
+ background: colorAlpha($palette_t1, 0.08);
89
+ }
90
+ }
91
+
92
+ .label {
93
+ color: $palette_t7;
94
+ font-size: 12px;
95
+ }
86
96
  ```
87
97
 
88
- **Breakpoints:**
98
+ **Import in component:**
89
99
 
90
100
  ```javascript
91
- [theme.breakpoints.down('sm')]: { padding: theme.spacing(1) } // max-width: 599px
92
- [theme.breakpoints.up('md')]: { flexDirection: 'row' } // min-width: 960px
101
+ import styles from './App.scss';
102
+ // Use: <div className={styles.container}>
93
103
  ```
94
104
 
95
- **`theme.zIndex`** stacking constants: `appBar` (1100), `drawer` (1200), `modal` (1300), `tooltip` (1500).
105
+ **Colors & Theming:**
96
106
 
97
- **CSS variables** available in `.css` files:
107
+ NEVER hardcode color values (hex, rgb, hsl). Always use `@corva/ui` theme colors:
98
108
 
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
- ```
109
+ - In SCSS: use SCSS variables from `@corva/ui/styles/common` like `$palette_t1`, `$palette_b5`, `$palette_t7`
110
+ - Use `colorAlpha($color, $opacity)` for transparency instead of raw `rgba()`
111
+ - Run MCP tool `get_theme_docs` (section: "variables") to see all available theme variables
112
+ - Run MCP tool `get_theme_docs` (section: "palette") to see available palette colors with hex values
104
113
 
105
- **SCSS functions** — available in `.module.scss` files:
114
+ **Rules:**
106
115
 
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
- ```
116
+ - No inline `style={{...}}` unless absolutely necessary for dynamic values
117
+ - Use `classnames` for conditional/composed classes, never manual string joins
118
+ - No global selectors (tag selectors like `div`, `span`) use `:global()` only when absolutely necessary
119
+ - No `!important`
120
+ - Use a descriptive camelCase class as the root selector (e.g. `.toolbar`, `.chartPanel`) instead of generic `.root`
118
121
 
119
122
  ### State Management (Zustand)
120
123
 
@@ -4,7 +4,7 @@ import { useAppCommons } from '@corva/ui/effects';
4
4
  import { DEFAULT_SETTINGS } from './constants';
5
5
  import logo from './assets/logo.svg';
6
6
 
7
- import styles from './App.css';
7
+ import styles from './App.scss';
8
8
 
9
9
  const App = () => {
10
10
  const { appKey, fracFleet, well, wells, appSettings } = useAppCommons();
@@ -4,7 +4,7 @@ import { useAppCommons } from '@corva/ui/effects';
4
4
  import { DEFAULT_SETTINGS } from './constants';
5
5
  import logo from './assets/logo.svg';
6
6
 
7
- import styles from './App.css';
7
+ import styles from './App.scss';
8
8
 
9
9
  const App = () => {
10
10
  const { appKey, rig, well, appSettings } = useAppCommons();
@@ -22,7 +22,7 @@ src/
22
22
  constants.ts — Default settings values
23
23
  types.ts — Custom app settings interface
24
24
  custom.d.ts — Module declarations for CSS/SVG imports
25
- App.css — Component styles (CSS modules)
25
+ App.scss — Component styles (SCSS modules)
26
26
  __tests__/ — Jest test files
27
27
  __mocks__/mockData.ts — Mock data for tests
28
28
  assets/ — Static assets (SVGs, images)
@@ -57,83 +57,86 @@ const AppSettings = () => {
57
57
 
58
58
  ### Styling
59
59
 
60
- CSS modules for component styles:
60
+ The preferred way to write styles is SCSS (`.scss` files).
61
61
 
62
- ```typescript
63
- import styles from './App.css';
64
- // Use: <div className={styles.container}>
62
+ Every `.scss` file must import the shared utilities:
63
+
64
+ ```scss
65
+ @import '@corva/ui/styles/common';
65
66
  ```
66
67
 
67
- For custom component styles, use `makeStyles` from MUI v4:
68
+ This provides all functions, variables, and mixins below.
68
69
 
69
- ```typescript
70
- import { makeStyles } from '@material-ui/core/styles';
70
+ For custom shared variables or mixins specific to the project, create a `src/styles/` directory (e.g. `_variables.scss`, `_common.scss`) and import them alongside the `@corva/ui` import.
71
71
 
72
- const useStyles = makeStyles(theme => ({
73
- root: { display: 'flex', gap: theme.spacing(1) },
74
- }));
72
+ **`spacing($top, $right?, $bottom?, $left?)`** 8px base unit:
75
73
 
76
- // Inside component:
77
- const styles = useStyles();
78
- return <div className={styles.root}>...</div>;
74
+ ```scss
75
+ padding: spacing(2); // 16px
76
+ margin: spacing(1, 0); // 8px 0
77
+ gap: spacing(0.5); // 4px
78
+ padding: spacing(2, 1, 2, 1); // 16px 8px 16px 8px
79
79
  ```
80
80
 
81
- **`theme.spacing(n)`** — 8px base unit for layout dimensions:
81
+ **`colorAlpha($color, $opacity)`** — transparency:
82
82
 
83
- ```typescript
84
- padding: theme.spacing(2) // 16px
85
- margin: theme.spacing(1, 0) // 8px 0
86
- borderRadius: theme.spacing(0.5) // 4px
83
+ ```scss
84
+ background: colorAlpha($palette_t1, 0.08); // white at 8% opacity
85
+ border: 1px solid colorAlpha($palette_t1, 0.12);
87
86
  ```
88
87
 
89
- **Color manipulation** `fade`, `darken`, `lighten` from `@material-ui/core/styles`:
90
-
91
- ```typescript
92
- import { fade, darken, lighten } from '@material-ui/core/styles';
88
+ **`transition($properties...)`**standard `cubic-bezier(0.4, 0, 0.2, 1) 0.15s`:
93
89
 
94
- background: fade(theme.palette.common.white, 0.08) // white at 8% opacity
95
- color: darken(theme.palette.primary.main, 0.2) // darken by 20%
90
+ ```scss
91
+ transition: transition(opacity);
92
+ transition: transition(color, background-color);
96
93
  ```
97
94
 
98
- **Transitions:**
95
+ **Full example:**
99
96
 
100
- ```typescript
101
- transition: theme.transitions.create('opacity')
102
- transition: theme.transitions.create(['opacity', 'background-color'], {
103
- duration: theme.transitions.duration.standard, // 300ms
104
- })
97
+ ```scss
98
+ @import '@corva/ui/styles/common';
99
+
100
+ .container {
101
+ padding: spacing(2);
102
+ background: $palette_b5;
103
+ color: $palette_t1;
104
+ transition: transition(opacity, background-color);
105
+
106
+ &:hover {
107
+ background: colorAlpha($palette_t1, 0.08);
108
+ }
109
+ }
110
+
111
+ .label {
112
+ color: $palette_t7;
113
+ font-size: 12px;
114
+ }
105
115
  ```
106
116
 
107
- **Breakpoints:**
117
+ **Import in component:**
108
118
 
109
119
  ```typescript
110
- [theme.breakpoints.down('sm')]: { padding: theme.spacing(1) } // max-width: 599px
111
- [theme.breakpoints.up('md')]: { flexDirection: 'row' } // min-width: 960px
120
+ import styles from './App.scss';
121
+ // Use: <div className={styles.container}>
112
122
  ```
113
123
 
114
- **`theme.zIndex`** stacking constants: `appBar` (1100), `drawer` (1200), `modal` (1300), `tooltip` (1500).
115
-
116
- **CSS variables** — available in `.css` files:
124
+ **Colors & Theming:**
117
125
 
118
- ```css
119
- color: var(--palette-primary-text-1); /* primary text */
120
- background: var(--palette-background-b-5); /* card background */
121
- border-color: var(--palette-primary-text-9); /* borders */
122
- ```
126
+ NEVER hardcode color values (hex, rgb, hsl). Always use `@corva/ui` theme colors:
123
127
 
124
- **SCSS functions** available in `.module.scss` files:
128
+ - In SCSS: use SCSS variables from `@corva/ui/styles/common` like `$palette_t1`, `$palette_b5`, `$palette_t7`
129
+ - Use `colorAlpha($color, $opacity)` for transparency instead of raw `rgba()`
130
+ - Run MCP tool `get_theme_docs` (section: "variables") to see all available theme variables
131
+ - Run MCP tool `get_theme_docs` (section: "palette") to see available palette colors with hex values
125
132
 
126
- ```scss
127
- padding: spacing(2); // 16px
128
- color: colorAlpha($color, 0.5); // rgba transparency
129
- transition: transition(opacity); // standard cubic-bezier
130
- ```
133
+ **Rules:**
131
134
 
132
- **Direct theme import:**
133
-
134
- ```typescript
135
- import { lightTheme, darkTheme } from '@corva/ui/config';
136
- ```
135
+ - No inline `style={{...}}` unless absolutely necessary for dynamic values
136
+ - Use `classnames` for conditional/composed classes, never manual string joins
137
+ - No global selectors (tag selectors like `div`, `span`) — use `:global()` only when absolutely necessary
138
+ - No `!important`
139
+ - Use a descriptive camelCase class as the root selector (e.g. `.toolbar`, `.chartPanel`) instead of generic `.root`
137
140
 
138
141
  ### State Management (Zustand)
139
142
 
@@ -216,6 +219,7 @@ const selectedChannels = useAppStoreSelector(state => state.selectedChannels);
216
219
  **Wire it up in `ParentApp`** (see full example with `QueryClientProvider` in the Data Fetching section below).
217
220
 
218
221
  General rules:
222
+
219
223
  - Name stores `use[Name]Store` (e.g., `useWellDataStore`)
220
224
  - Use selectors: `const value = useAppStore('value')`
221
225
  - Keep business logic in store actions
@@ -277,6 +281,7 @@ export default { component: ParentApp, settings: AppSettings };
277
281
  ```
278
282
 
279
283
  General rules:
284
+
280
285
  - Encapsulate queries in custom hooks (e.g., `useWellData`)
281
286
  - Use typed query keys (array format)
282
287
  - Always handle `isLoading` and `isError` states
@@ -4,7 +4,7 @@ import { useAppCommons } from '@corva/ui/effects';
4
4
  import { DEFAULT_SETTINGS } from './constants';
5
5
  import logo from './assets/logo.svg';
6
6
 
7
- import styles from './App.css';
7
+ import styles from './App.scss';
8
8
 
9
9
  const App = () => {
10
10
  const { appKey, fracFleet, well, wells, appSettings } = useAppCommons();
@@ -4,7 +4,7 @@ import { useAppCommons } from '@corva/ui/effects';
4
4
  import { DEFAULT_SETTINGS } from './constants';
5
5
  import logo from './assets/logo.svg';
6
6
 
7
- import styles from './App.css';
7
+ import styles from './App.scss';
8
8
 
9
9
  const App = () => {
10
10
  const { appKey, rig, well, appSettings } = useAppCommons();