@genesislcap/blank-app-seed 5.22.2-prerelease.2 → 5.23.1

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.
Files changed (194) hide show
  1. package/.genx/configure.js +11 -1
  2. package/.genx/package.json +1 -1
  3. package/.genx/scripts/generate-test-apps.sh +124 -0
  4. package/.genx/templates/angular/chart.hbs +19 -7
  5. package/.genx/templates/angular/component/component.column.defs.hbs +16 -4
  6. package/.genx/templates/angular/component/component.create.form.hbs +10 -2
  7. package/.genx/templates/angular/component/component.events.config.hbs +31 -6
  8. package/.genx/templates/angular/component/component.filter.form.hbs +1 -2
  9. package/.genx/templates/angular/component/component.gridOptions.hbs +15 -3
  10. package/.genx/templates/angular/component/component.hbs +77 -46
  11. package/.genx/templates/angular/component/component.index.hbs +1 -1
  12. package/.genx/templates/angular/component/component.update.form.hbs +1 -1
  13. package/.genx/templates/angular/entityManager.hbs +6 -6
  14. package/{client-tmp/angular/src/app/utils/fdc3.ts → .genx/templates/angular/fdc3.ts.hbs} +6 -8
  15. package/.genx/templates/angular/form.hbs +9 -12
  16. package/.genx/templates/angular/grid.hbs +43 -40
  17. package/.genx/templates/angular/route.hbs +8 -2
  18. package/.genx/templates/angular/route.template.hbs +7 -7
  19. package/.genx/templates/angular/slices/eventing.slice.hbs +21 -10
  20. package/.genx/templates/angular/store.hbs +1 -5
  21. package/.genx/templates/angular/tabsPanel.hbs +2 -2
  22. package/.genx/templates/react/chart.hbs +6 -15
  23. package/.genx/templates/react/component/component.column.defs.hbs +16 -10
  24. package/.genx/templates/react/component/component.create.form.hbs +2 -2
  25. package/.genx/templates/react/component/component.events.config.hbs +32 -5
  26. package/.genx/templates/react/component/component.filter.form.hbs +1 -2
  27. package/.genx/templates/react/component/component.gridOptions.hbs +17 -11
  28. package/.genx/templates/react/component/component.hbs +84 -42
  29. package/.genx/templates/react/component/component.update.form.hbs +1 -1
  30. package/.genx/templates/react/entityManager.hbs +8 -16
  31. package/{client-tmp/react/src/utils/fdc3.ts → .genx/templates/react/fdc3.ts.hbs} +7 -8
  32. package/.genx/templates/react/form.hbs +1 -15
  33. package/.genx/templates/react/grid.hbs +11 -11
  34. package/.genx/templates/react/gridLayout.hbs +44 -4
  35. package/.genx/templates/react/horizontalLayout.hbs +10 -1
  36. package/.genx/templates/react/route.hbs +23 -16
  37. package/.genx/templates/react/slices/eventing.slice.hbs +19 -8
  38. package/.genx/templates/react/store.hbs +1 -5
  39. package/.genx/templates/react/tabsLayout.hbs +5 -1
  40. package/.genx/templates/web-components/chart.hbs +25 -25
  41. package/.genx/templates/web-components/component/component.column.defs.hbs +11 -3
  42. package/.genx/templates/web-components/component/component.create.form.hbs +11 -4
  43. package/.genx/templates/web-components/component/component.events.config.hbs +33 -6
  44. package/.genx/templates/web-components/component/component.filter.form.hbs +1 -2
  45. package/.genx/templates/web-components/component/component.gridOptions.hbs +12 -3
  46. package/.genx/templates/web-components/component/component.hbs +19 -20
  47. package/.genx/templates/web-components/component/component.index.hbs +1 -1
  48. package/.genx/templates/web-components/component/component.template.hbs +9 -13
  49. package/.genx/templates/web-components/component/component.update.form.hbs +1 -1
  50. package/.genx/templates/web-components/entityManager.hbs +92 -73
  51. package/{client-tmp/web-components/src/utils/fdc3.ts → .genx/templates/web-components/fdc3.ts.hbs} +6 -7
  52. package/.genx/templates/web-components/form.hbs +16 -15
  53. package/.genx/templates/web-components/grid.hbs +49 -41
  54. package/.genx/templates/web-components/gridLayout.hbs +32 -28
  55. package/.genx/templates/web-components/horizontalLayout.hbs +5 -5
  56. package/.genx/templates/web-components/route.template.hbs +4 -6
  57. package/.genx/templates/web-components/slices/eventing.slice.hbs +23 -15
  58. package/.genx/templates/web-components/store.hbs +1 -9
  59. package/.genx/templates/web-components/tabsLayout.hbs +5 -5
  60. package/.genx/tests/fixtures/routes-full.json +299 -0
  61. package/.genx/tests/fixtures/testapp/client/src/pages/Home/Home.tsx +12 -0
  62. package/.genx/utils/formatJSONValue.js +38 -2
  63. package/.genx/utils/formatRouteData.js +67 -18
  64. package/.genx/utils/generateTile.js +10 -1
  65. package/.genx/utils/gridSerializers.js +63 -23
  66. package/.genx/utils/registerPartials.js +15 -12
  67. package/.genx/utils/toJsStringLiteral.js +37 -0
  68. package/.genx/versions.json +3 -3
  69. package/.github/workflows/build.yml +22 -1
  70. package/.husky/pre-commit +8 -0
  71. package/CHANGELOG.md +97 -23
  72. package/bdd-tests/gradle/wrapper/gradle-wrapper.properties +1 -1
  73. package/bdd-tests/gradle.properties +0 -1
  74. package/client-tmp/angular/.oxfmtrc.json +32 -0
  75. package/client-tmp/angular/.oxlintrc.json +4 -0
  76. package/client-tmp/angular/README.md +4 -4
  77. package/client-tmp/angular/jest.config.js +1 -1
  78. package/client-tmp/angular/lint-css.js +24 -3
  79. package/client-tmp/angular/oxlint-staged.js +45 -0
  80. package/client-tmp/angular/package.json +19 -9
  81. package/client-tmp/angular/playwright.config.ts +12 -11
  82. package/client-tmp/angular/src/app/app.component.html +0 -1
  83. package/client-tmp/angular/src/app/app.component.ts +8 -9
  84. package/client-tmp/angular/src/app/app.config.ts +2 -2
  85. package/client-tmp/angular/src/app/app.module.ts +6 -5
  86. package/client-tmp/angular/src/app/components/error-message/error-message.component.html +29 -15
  87. package/client-tmp/angular/src/app/components/error-message/error-message.component.spec.ts +2 -2
  88. package/client-tmp/angular/src/app/components/error-message/error-message.component.ts +3 -5
  89. package/client-tmp/angular/src/app/directive/app-lazy-load.directive.ts +1 -0
  90. package/client-tmp/angular/src/app/guards/chained.guard.ts +19 -19
  91. package/client-tmp/angular/src/app/guards/connection.guard.ts +12 -12
  92. package/client-tmp/angular/src/app/layouts/blank/blank.layout.spec.ts +1 -1
  93. package/client-tmp/angular/src/app/layouts/blank/blank.layout.ts +1 -5
  94. package/client-tmp/angular/src/app/layouts/default/default.layout.html +3 -4
  95. package/client-tmp/angular/src/app/layouts/default/default.layout.spec.ts +4 -4
  96. package/client-tmp/angular/src/app/layouts/default/default.layout.ts +1 -1
  97. package/client-tmp/angular/src/app/pages/auth-login/auth-login.component.ts +1 -2
  98. package/client-tmp/angular/src/app/pages/not-permitted/not-permitted.component.html +3 -3
  99. package/client-tmp/angular/src/app/pages/not-permitted/not-permitted.component.scss +1 -1
  100. package/client-tmp/angular/src/app/pages/not-permitted/not-permitted.component.spec.ts +5 -5
  101. package/client-tmp/angular/src/app/pages/not-permitted/not-permitted.component.ts +3 -4
  102. package/client-tmp/angular/src/app/services/route.service.ts +79 -74
  103. package/client-tmp/angular/src/app/share/foundation-auth.ts +7 -1
  104. package/client-tmp/angular/src/app/share/genesis-components.ts +66 -68
  105. package/client-tmp/angular/src/app/utils/index.ts +3 -1
  106. package/client-tmp/angular/src/app/utils/permissions.ts +7 -7
  107. package/client-tmp/angular/src/declaration.d.ts +1 -1
  108. package/client-tmp/angular/src/environments/environment.ts +1 -1
  109. package/client-tmp/angular/src/jest.setup.ts +8 -0
  110. package/client-tmp/angular/src/main.ts +2 -2
  111. package/client-tmp/angular/src/pbc/container.ts +28 -26
  112. package/client-tmp/angular/src/pbc/elementsRenderer.ts +49 -39
  113. package/client-tmp/angular/src/pbc/utils.ts +61 -60
  114. package/client-tmp/angular/src/styles/_mixins.scss +7 -8
  115. package/client-tmp/angular/src/styles/styles.css +6 -6
  116. package/client-tmp/angular/test/e2e/fixture.ts +2 -2
  117. package/client-tmp/angular/test/e2e/flows/001-protected.e2e.ts +1 -1
  118. package/client-tmp/angular/tsconfig.app.json +2 -8
  119. package/client-tmp/angular/tsconfig.json +1 -4
  120. package/client-tmp/angular/tsconfig.spec.json +4 -11
  121. package/client-tmp/angular/webpack.dev.config.js +3 -4
  122. package/client-tmp/angular/webpack.prod.config.js +3 -4
  123. package/client-tmp/angular/webpack.shared.config.js +20 -24
  124. package/client-tmp/react/.oxfmtrc.json +30 -0
  125. package/client-tmp/react/.oxlintrc.json +4 -0
  126. package/client-tmp/react/README.md +4 -4
  127. package/client-tmp/react/jest.config.ts +8 -4
  128. package/client-tmp/react/jest.setup.ts +85 -1
  129. package/client-tmp/react/oxlint-staged.js +45 -0
  130. package/client-tmp/react/package.json +24 -9
  131. package/client-tmp/react/playwright.config.ts +11 -10
  132. package/client-tmp/react/src/App.tsx +27 -29
  133. package/client-tmp/react/src/__mocks__/fileMock.js +1 -0
  134. package/client-tmp/react/src/components/ErrorMessage/ErrorMessage.test.tsx +16 -4
  135. package/client-tmp/react/src/components/ErrorMessage/ErrorMessage.tsx +32 -29
  136. package/client-tmp/react/src/components/error-boundary/ErrorBoundary.tsx +15 -6
  137. package/client-tmp/react/src/components/routes/AppRoutes.tsx +1 -1
  138. package/client-tmp/react/src/components/single-component/registry.ts +4 -0
  139. package/client-tmp/react/src/custom-elements.d.ts +4 -2
  140. package/client-tmp/react/src/helpers/hasPermissionHelper.tsx +1 -1
  141. package/client-tmp/react/src/helpers/isAuthenticatedHelper.tsx +1 -1
  142. package/client-tmp/react/src/helpers/isConnectedHelper.tsx +3 -3
  143. package/client-tmp/react/src/layouts/blank/BlankLayout.tsx +7 -11
  144. package/client-tmp/react/src/layouts/default/DefaultLayout.tsx +8 -8
  145. package/client-tmp/react/src/main.tsx +11 -11
  146. package/client-tmp/react/src/pages/AuthPage/AuthPage.test.tsx +13 -2
  147. package/client-tmp/react/src/pages/AuthPage/AuthPage.tsx +14 -13
  148. package/client-tmp/react/src/pages/NotFoundPage/NotFoundPage.tsx +2 -7
  149. package/client-tmp/react/src/pages/NotPermittedPage/NotPermittedPage.test.tsx +8 -4
  150. package/client-tmp/react/src/pages/NotPermittedPage/NotPermittedPage.tsx +1 -1
  151. package/client-tmp/react/src/pbc/container.tsx +9 -4
  152. package/client-tmp/react/src/pbc/elementsRenderer.tsx +18 -9
  153. package/client-tmp/react/src/pbc/utils.ts +60 -59
  154. package/client-tmp/react/src/services/auth.service.ts +1 -1
  155. package/client-tmp/react/src/services/store.service.ts +5 -3
  156. package/client-tmp/react/src/share/foundation-login.ts +2 -2
  157. package/client-tmp/react/src/share/genesis-components.ts +46 -47
  158. package/client-tmp/react/src/store/AuthContext.tsx +2 -10
  159. package/client-tmp/react/src/store/RoutesContext.test.tsx +99 -0
  160. package/client-tmp/react/src/store/RoutesContext.tsx +25 -5
  161. package/client-tmp/react/src/styles/styles.css +7 -7
  162. package/client-tmp/react/src/types/LayoutName.ts +1 -1
  163. package/client-tmp/react/src/utils/getLayoutNameByRoute.ts +1 -1
  164. package/client-tmp/react/src/utils/index.ts +2 -0
  165. package/client-tmp/react/src/utils/layout.ts +8 -1
  166. package/client-tmp/react/src/utils/permissions.ts +7 -7
  167. package/client-tmp/react/src/utils/useDocumentTitle.ts +5 -6
  168. package/client-tmp/react/test/e2e/fixture.ts +5 -2
  169. package/client-tmp/react/test/e2e/flows/001-protected.e2e.ts +3 -1
  170. package/client-tmp/react/tsconfig.app.json +1 -1
  171. package/client-tmp/react/tsconfig.json +1 -5
  172. package/client-tmp/web-components/.oxfmtrc.json +30 -0
  173. package/client-tmp/web-components/.oxlintrc.json +4 -0
  174. package/client-tmp/web-components/oxlint-staged.js +45 -0
  175. package/client-tmp/web-components/package.json +14 -22
  176. package/client-tmp/web-components/src/components/components.ts +9 -7
  177. package/client-tmp/web-components/src/main/main.css +1 -2
  178. package/client-tmp/web-components/src/main/main.ts +1 -1
  179. package/client-tmp/web-components/src/routes/config.ts +4 -0
  180. package/client-tmp/web-components/src/utils/index.ts +2 -0
  181. package/client-tmp/web-components/tsconfig.json +1 -4
  182. package/gradle/wrapper/gradle-wrapper.properties +1 -1
  183. package/package.json +1 -1
  184. package/server/gradle/wrapper/gradle-wrapper.properties +1 -1
  185. package/server/gradle.properties +0 -2
  186. package/.genx/templates/angular/gridLayout.hbs +0 -30
  187. package/.genx/templates/angular/horizontalLayout.hbs +0 -7
  188. package/.genx/templates/angular/tabsLayout.hbs +0 -7
  189. package/.genx/tests/fixtures/testapp/client/src/routes/home/home.template.ts +0 -6
  190. package/client-tmp/react/.babelrc +0 -10
  191. package/client-tmp/react/.eslintrc.cjs +0 -21
  192. package/client-tmp/react/git +0 -0
  193. package/client-tmp/react/webpack.config.js +0 -121
  194. package/client-tmp/web-components/.prettierignore +0 -14
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "extends": ["./node_modules/@genesislcap/oxlint-config/.oxlintrc.json"]
4
+ }
@@ -27,7 +27,8 @@ Run `npm run test:e2e` to execute the end-to-end tests.
27
27
 
28
28
  ## Linting
29
29
 
30
- Run `npm run lint` to lint the project.
30
+ Run `npm run lint` to lint the project (Oxlint → Oxfmt → Stylelint via `genx lint -l ox`).
31
+ Run `npm run lint:fix` to auto-fix lint findings and formatting.
31
32
 
32
33
  ## Styling
33
34
 
@@ -52,9 +53,8 @@ modes, and the selected mode is remembered across reloads.
52
53
  - `npm run dev:no-open`: Serves the project in development mode without automatically opening the browser.
53
54
  - `npm run dev:intellij`: Serves the project in development mode (specific for IntelliJ).
54
55
  - `npm run dev:https`: Serves the project in development mode with HTTPS.
55
- - `npm run lint`: Lints the project using the specified profile.
56
- - `npm run lint:fix`: Fixes linting errors in the project.
57
- - `npm run lint:eslint`: Lints the project using ESLint and the specified profile.
56
+ - `npm run lint`: Lints the project (Oxlint Oxfmt → Stylelint).
57
+ - `npm run lint:fix`: Fixes lint findings and formatting.
58
58
  - `npm run lint:stylelint`: Lints CSS using a custom script.
59
59
  - `npm run test`: Runs unit tests.
60
60
  - `npm run test:e2e`: Runs end-to-end tests.
@@ -4,12 +4,16 @@ const config: Config.InitialOptions = {
4
4
  testEnvironment: 'jsdom',
5
5
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
6
6
  transform: {
7
- '^.+\\.jsx?$': 'babel-jest',
7
+ '^.+\\.jsx?$': ['babel-jest', { presets: ['@babel/preset-env'] }],
8
8
  '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.app.json' }],
9
9
  },
10
+ moduleNameMapper: {
11
+ '\\.(css|jpg|jpeg|png|gif|svg|woff2?)$': '<rootDir>/src/__mocks__/fileMock.js',
12
+ },
13
+ transformIgnorePatterns: ['node_modules/(?!(@genesislcap/*|consola|@microsoft|exenv-es6|uuid))'],
10
14
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
11
- testMatch: ["**/?(*.)+(test).[jt]s?(x)"],
12
- watchPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/dist/"],
15
+ testMatch: ['**/?(*.)+(test).[jt]s?(x)'],
16
+ watchPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],
13
17
  };
14
18
 
15
- export default config;
19
+ export default config;
@@ -1 +1,85 @@
1
- import '@testing-library/jest-dom';
1
+ import { TextDecoder, TextEncoder } from 'node:util';
2
+ import '@testing-library/jest-dom';
3
+
4
+ // jsdom does not provide TextEncoder/TextDecoder, which react-router needs at import time.
5
+ if (typeof globalThis.TextEncoder === 'undefined') {
6
+ Object.assign(globalThis, { TextEncoder, TextDecoder });
7
+ }
8
+
9
+ // Mock window.matchMedia (fast-foundation reads it at import time)
10
+ Object.defineProperty(window, 'matchMedia', {
11
+ writable: true,
12
+ value: (query: string) => ({
13
+ matches: false,
14
+ media: query,
15
+ onchange: null,
16
+ addListener: jest.fn(), // Deprecated
17
+ removeListener: jest.fn(), // Deprecated
18
+ addEventListener: jest.fn(),
19
+ removeEventListener: jest.fn(),
20
+ dispatchEvent: jest.fn(),
21
+ }),
22
+ });
23
+
24
+ // Mock BroadcastChannel
25
+ class MockBroadcastChannel {
26
+ name: string;
27
+ onmessage: ((this: BroadcastChannel, ev: MessageEvent) => any) | null = null;
28
+ onmessageerror: ((this: BroadcastChannel, ev: MessageEvent) => any) | null = null;
29
+
30
+ constructor(name: string) {
31
+ this.name = name;
32
+ }
33
+
34
+ postMessage(message: any) {
35
+ if (this.onmessage) {
36
+ this.onmessage({ data: message } as MessageEvent);
37
+ }
38
+ }
39
+
40
+ close() {}
41
+
42
+ addEventListener() {}
43
+ removeEventListener() {}
44
+ dispatchEvent() {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ Object.defineProperty(window, 'BroadcastChannel', {
50
+ writable: true,
51
+ value: MockBroadcastChannel,
52
+ });
53
+
54
+ // jsdom lacks the constructable-stylesheet API used by the modal-theme runtime
55
+ // (CSSStyleSheet.replaceSync + Document.adoptedStyleSheets)
56
+ if (typeof CSSStyleSheet === 'undefined') {
57
+ Object.defineProperty(window, 'CSSStyleSheet', {
58
+ writable: true,
59
+ value: class {
60
+ replaceSync() {}
61
+ },
62
+ });
63
+ } else if (!CSSStyleSheet.prototype.replaceSync) {
64
+ CSSStyleSheet.prototype.replaceSync = () => {};
65
+ }
66
+
67
+ if (!('adoptedStyleSheets' in document)) {
68
+ Object.defineProperty(document, 'adoptedStyleSheets', {
69
+ writable: true,
70
+ value: [],
71
+ });
72
+ }
73
+
74
+ // jsdom lacks structuredClone, which the design-system theme merge uses
75
+ if (typeof globalThis.structuredClone === 'undefined') {
76
+ Object.defineProperty(globalThis, 'structuredClone', {
77
+ writable: true,
78
+ value: (value: unknown) => JSON.parse(JSON.stringify(value)),
79
+ });
80
+ }
81
+
82
+ // jsdom's sandbox does not expose Node's global fetch; foundation-auth references it at import time.
83
+ if (typeof globalThis.fetch === 'undefined') {
84
+ Object.defineProperty(globalThis, 'fetch', { writable: true, value: jest.fn() });
85
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Lints the staged files, fixing what can be fixed and failing on what cannot.
3
+ *
4
+ * `oxlint --fix` is not single-pass convergent: it applies at most one fix per
5
+ * overlapping span, so a file with several unused specifiers in one import
6
+ * statement still reports the rest afterwards. A single
7
+ * `oxlint --fix --deny-warnings` would therefore reject a commit whose findings
8
+ * are entirely auto-fixable, and lint-staged would revert the partial fix. There
9
+ * is no `--fix-passes` flag (oxlint 1.78), so the loop lives here; it can go
10
+ * back to one plain call once oxlint converges on its own.
11
+ *
12
+ * The fixing passes stay quiet so a remaining finding is reported once, by the
13
+ * final pass, rather than once per attempt.
14
+ */
15
+ const { spawnSync } = require('node:child_process');
16
+
17
+ /** Convergence needs about log2(findings) passes; this is well clear of that. */
18
+ const MAX_PASSES = 10;
19
+
20
+ const files = process.argv.slice(2);
21
+
22
+ const runOxlint = (args, quiet) =>
23
+ spawnSync('oxlint', [...args, ...files], {
24
+ stdio: quiet ? 'ignore' : 'inherit',
25
+ shell: process.platform === 'win32',
26
+ });
27
+
28
+ if (files.length === 0) {
29
+ process.exit(0);
30
+ }
31
+
32
+ for (let pass = 0; pass < MAX_PASSES; pass += 1) {
33
+ const { status, error } = runOxlint(['--fix', '--deny-warnings'], true);
34
+ if (error) {
35
+ // Never fail silently: a missing binary must not look like a lint failure.
36
+ console.error(`oxlint-staged: could not run oxlint (${error.code})`);
37
+ process.exit(1);
38
+ }
39
+ if (status === 0) {
40
+ process.exit(0);
41
+ }
42
+ }
43
+
44
+ // Still not clean: report what is left (without touching the files again) and fail.
45
+ process.exit(runOxlint(['--deny-warnings'], false).status === 0 ? 0 : 1);
@@ -28,11 +28,10 @@
28
28
  "dev:no-open": "NO_OPEN=true npm run dev",
29
29
  "dev:intellij": "npm run dev",
30
30
  "dev:https": "HTTPS=true npm run dev",
31
- "lint": "genx lint --profile",
32
- "lint:fix": "genx lint --fix",
33
- "lint:eslint": "genx lint -l eslint --profile",
31
+ "git:setup": "cd .. && npx --no --prefix client husky install",
32
+ "lint": "genx lint -l ox",
33
+ "lint:fix": "genx lint -l ox --fix",
34
34
  "lint:stylelint": "genx lint -l stylelint",
35
- "stats": "webpack --mode production --json > stats.json",
36
35
  "test": "jest",
37
36
  "test:coverage": "jest --coverage",
38
37
  "test:unit:watch": "jest --watchAll",
@@ -40,7 +39,18 @@
40
39
  "test:e2e:debug": "genx test --e2e --debug",
41
40
  "test:e2e:ui": "genx test --e2e --interactive"
42
41
  },
43
- "prettier": "@genesislcap/prettier-config",
42
+ "lint-staged": {
43
+ "*.{ts,js,tsx,jsx}": [
44
+ "node oxlint-staged.js",
45
+ "oxfmt --write"
46
+ ],
47
+ "*.{mjs,cjs}": [
48
+ "oxfmt --write"
49
+ ]
50
+ },
51
+ "stylelint": {
52
+ "extends": "@genesislcap/stylelint-config"
53
+ },
44
54
  "dependencies": {
45
55
  "@microsoft/fast-react-wrapper": ">=0.3.25",
46
56
  "ag-grid-community": "36.0.0",
@@ -60,6 +70,7 @@
60
70
  "@genesislcap/foundation-notifications": "{{versions.UI}}",
61
71
  "@genesislcap/foundation-react-utils": "{{versions.UI}}",
62
72
  "@genesislcap/foundation-shell": "{{versions.UI}}",
73
+ "@genesislcap/foundation-store": "{{versions.UI}}",
63
74
  "@genesislcap/foundation-ui": "{{versions.UI}}",
64
75
  "@genesislcap/foundation-user": "{{versions.UI}}",
65
76
  "@genesislcap/foundation-utils": "{{versions.UI}}",
@@ -91,13 +102,15 @@
91
102
  "@analytics/core": "0.12.17"
92
103
  },
93
104
  "devDependencies": {
105
+ "@babel/core": "^7.24.0",
106
+ "@babel/preset-env": "^7.24.0",
94
107
  "@genesislcap/design-system-configurator": "{{versions.UI}}",
95
- "@genesislcap/eslint-config": "{{versions.UI}}",
96
- "@genesislcap/prettier-config": "{{versions.UI}}",
97
108
  "@genesislcap/foundation-testing": "{{versions.UI}}",
98
109
  "@genesislcap/genx": "{{versions.UI}}",
99
110
  "@genesislcap/event-type-codegen": "{{versions.UI}}",
100
111
  "@genesislcap/build-kit": "{{versions.UI}}",
112
+ "@genesislcap/oxlint-config": "{{versions.UI}}",
113
+ "@genesislcap/stylelint-config": "{{versions.UI}}",
101
114
  "@testing-library/dom": "^10.4.0",
102
115
  "@testing-library/jest-dom": "^6.5.0",
103
116
  "@testing-library/react": "^16.1.0",
@@ -106,10 +119,12 @@
106
119
  "@types/node": "^20.14.10",
107
120
  "@types/react": "^19.2.0",
108
121
  "@types/react-dom": "^19.2.0",
109
- "eslint-plugin-react-hooks": "^5.1.0",
110
- "eslint-plugin-react-refresh": "^0.4.16",
122
+ "husky": "^7.0.4",
111
123
  "jest": "^29.7.0",
112
124
  "jest-environment-jsdom": "^29.7.0",
125
+ "lint-staged": "^12.4.1",
126
+ "oxfmt": "^0.44.0",
127
+ "oxlint": "^1.77.0",
113
128
  "ts-jest": "^29.2.5",
114
129
  "ts-node": "^10.9.2",
115
130
  "typescript": "5.9.2"
@@ -2,16 +2,17 @@ import { configDefaults } from '@genesislcap/foundation-testing/e2e';
2
2
 
3
3
  export const PROTOCOL = process.env['PROTOCOL'] || 'http';
4
4
  export const HOST = process.env['HOST'] || 'localhost';
5
+ // oxlint-disable-next-line no-magic-numbers -- 5173 is the standard Vite dev server port
5
6
  export const PORT: number = Number(process.env['PORT']) || 5173;
6
7
 
7
8
  export default {
8
- ...configDefaults,
9
- webServer: {
10
- ...configDefaults.webServer,
11
- url: `${PROTOCOL}://${HOST}:${PORT}`,
12
- },
13
- use: {
14
- ...configDefaults.use,
15
- baseURL: `${PROTOCOL}://${HOST}:${PORT}`,
16
- }
17
- };
9
+ ...configDefaults,
10
+ webServer: {
11
+ ...configDefaults.webServer,
12
+ url: `${PROTOCOL}://${HOST}:${PORT}`,
13
+ },
14
+ use: {
15
+ ...configDefaults.use,
16
+ baseURL: `${PROTOCOL}://${HOST}:${PORT}`,
17
+ },
18
+ };
@@ -1,39 +1,41 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
+ import { Provider } from 'react-redux';
2
3
  import { BrowserRouter as Router } from 'react-router-dom';
3
- {{#if FDC3.channels.length~}}
4
- import {
5
- listenToChannel,
6
- onFDC3Ready,
7
- } from './utils';
8
- {{/if}}
4
+ import AppRoutes from './components/routes/AppRoutes';
9
5
  import { customEventFactory, registerStylesTarget } from './pbc/utils';
10
- import { RoutesProvider } from './store/RoutesContext';
11
- import { registerComponents as genesisRegisterComponents } from './share/genesis-components';
12
6
  import { storeService } from './services/store.service';
13
- import AppRoutes from './components/routes/AppRoutes';
7
+ import { registerComponents as genesisRegisterComponents } from './share/genesis-components';
8
+ import { RoutesProvider } from './store/RoutesContext';
14
9
  import { reduxStore } from './store/store';
15
- import { Provider } from 'react-redux';
10
+ {{#if FDC3.channels.length~}}
11
+ import { listenToChannel, onFDC3Ready } from './utils';
12
+ {{/if}}
16
13
 
17
14
  interface AppProps {
18
15
  rootElement: HTMLElement;
19
16
  }
20
17
 
21
18
  const App: React.FC<AppProps> = ({ rootElement }) => {
22
- {{#if FDC3.channels.length~}}
23
- const FDC3ReadyHandler = () => {
19
+ {{#if FDC3.channels.length}}
20
+ // Memoized so the handler keeps a stable identity and the effect below registers the
21
+ // channel listeners once instead of on every render.
22
+ const FDC3ReadyHandler = useCallback(() => {
24
23
  {{#each FDC3.channels}}
25
24
  listenToChannel('{{this.name}}', '{{this.type}}', (result) => {
26
- console.log('Received FDC3 channel message on: {{this.name}} channel, type: {{this.type}}', result);
25
+ console.log('Received FDC3 message on {{this.name}} ({{this.type}})', result);
27
26
  // TODO: Add your listener logic here
28
27
  // E.g. open a modal or route to specific page: Route.path.push(`[Route name]`);
29
28
  });
30
29
  {{/each}}
31
- };
30
+ }, []);
32
31
  {{/if}}
33
32
  const [componentsReady, setComponentsReady] = useState(false);
34
- const dispatchCustomEvent = useCallback((type: string, detail?: unknown) => {
35
- rootElement.dispatchEvent(customEventFactory(type, detail));
36
- }, [rootElement]);
33
+ const dispatchCustomEvent = useCallback(
34
+ (type: string, detail?: unknown) => {
35
+ rootElement.dispatchEvent(customEventFactory(type, detail));
36
+ },
37
+ [rootElement],
38
+ );
37
39
 
38
40
  const handleStoreConnected = useCallback((event: CustomEvent) => {
39
41
  storeService.onConnected(event);
@@ -56,18 +58,14 @@ const App: React.FC<AppProps> = ({ rootElement }) => {
56
58
  rootElement.addEventListener('store-connected', handleStoreConnected as EventListener);
57
59
  dispatchCustomEvent('store-connected', rootElement);
58
60
  dispatchCustomEvent('store-ready', true);
59
-
60
- return () => {
61
- rootElement.removeEventListener(
62
- 'store-connected',
63
- handleStoreConnected as EventListener,
64
- );
65
- };
66
- {{#if FDC3.channels.length~}}
61
+ {{#if FDC3.channels.length}}
67
62
  onFDC3Ready(FDC3ReadyHandler);
68
63
  {{/if}}
69
64
 
70
- }, [rootElement, handleStoreConnected, dispatchCustomEvent]);
65
+ return () => {
66
+ rootElement.removeEventListener('store-connected', handleStoreConnected as EventListener);
67
+ };
68
+ }, [rootElement, handleStoreConnected, dispatchCustomEvent{{#if FDC3.channels.length}}, FDC3ReadyHandler{{/if}}]);
71
69
 
72
70
  const baseElement = document.querySelector('base');
73
71
  const basePath = baseElement?.getAttribute('href') || '';
@@ -79,9 +77,9 @@ const App: React.FC<AppProps> = ({ rootElement }) => {
79
77
  return (
80
78
  <Provider store={reduxStore}>
81
79
  <RoutesProvider>
82
- <Router basename={basePath}>
83
- <AppRoutes />
84
- </Router>
80
+ <Router basename={basePath}>
81
+ <AppRoutes />
82
+ </Router>
85
83
  </RoutesProvider>
86
84
  </Provider>
87
85
  );
@@ -0,0 +1 @@
1
+ module.exports = {};
@@ -42,7 +42,7 @@ describe('ErrorMessage Component', () => {
42
42
  render(<ErrorMessage message={message} />);
43
43
  const displayedMessage: HTMLElement = screen.getByText(message);
44
44
  const messageWrapper: HTMLElement | null = displayedMessage.parentElement;
45
-
45
+
46
46
  expect(messageWrapper).toBeInTheDocument();
47
47
  expect(messageWrapper).toHaveStyle(`
48
48
  color: var(--neutral-foreground-rest);
@@ -67,9 +67,21 @@ describe('ErrorMessage Component', () => {
67
67
  });
68
68
 
69
69
  test('renders the correct element type for various elementType props', () => {
70
- const elementTypes: ErrorMessageProps['elementType'][] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div'];
70
+ const elementTypes: ErrorMessageProps['elementType'][] = [
71
+ 'h1',
72
+ 'h2',
73
+ 'h3',
74
+ 'h4',
75
+ 'h5',
76
+ 'h6',
77
+ 'p',
78
+ 'span',
79
+ 'div',
80
+ ];
71
81
  elementTypes.forEach((elementType: ErrorMessageProps['elementType']) => {
72
- const { container }: { container: HTMLElement } = render(<ErrorMessage message={message} elementType={elementType} />);
82
+ const { container }: { container: HTMLElement } = render(
83
+ <ErrorMessage message={message} elementType={elementType} />,
84
+ );
73
85
  const displayedMessage: HTMLElement | null = container.querySelector(elementType as string);
74
86
  expect(displayedMessage).toBeInTheDocument();
75
87
  expect(displayedMessage?.tagName).toBe((elementType as string).toUpperCase());
@@ -92,4 +104,4 @@ describe('ErrorMessage Component', () => {
92
104
  const { container }: { container: HTMLElement } = render(<ErrorMessage message={undefined} />);
93
105
  expect(container.firstChild).toBeNull();
94
106
  });
95
- });
107
+ });
@@ -22,7 +22,7 @@ const styles = {
22
22
  width: 'fit-content',
23
23
  alignSelf: 'center',
24
24
  height: 'auto',
25
- maxHeight: '100%'
25
+ maxHeight: '100%',
26
26
  },
27
27
  };
28
28
 
@@ -34,34 +34,37 @@ export interface ErrorMessageProps {
34
34
  const ErrorMessage: React.FC<ErrorMessageProps> = ({ elementType = 'div', message = '' }) => {
35
35
  const ElementType = elementType;
36
36
 
37
- return message && message !== '' && (
38
- <section style={styles.errorMessageWrapper}>
39
- <div style={styles.errorMessage}>
40
- {(() => {
41
- switch (ElementType) {
42
- case 'h1':
43
- return <h1>{message}</h1>;
44
- case 'h2':
45
- return <h2>{message}</h2>;
46
- case 'h3':
47
- return <h3>{message}</h3>;
48
- case 'h4':
49
- return <h4>{message}</h4>;
50
- case 'h5':
51
- return <h5>{message}</h5>;
52
- case 'h6':
53
- return <h6>{message}</h6>;
54
- case 'p':
55
- return <p>{message}</p>;
56
- case 'span':
57
- return <span>{message}</span>;
58
- default:
59
- return <div>{message}</div>;
60
- }
61
- })()}
62
- </div>
63
- </section>
37
+ return (
38
+ message &&
39
+ message !== '' && (
40
+ <section style={styles.errorMessageWrapper}>
41
+ <div style={styles.errorMessage}>
42
+ {(() => {
43
+ switch (ElementType) {
44
+ case 'h1':
45
+ return <h1>{message}</h1>;
46
+ case 'h2':
47
+ return <h2>{message}</h2>;
48
+ case 'h3':
49
+ return <h3>{message}</h3>;
50
+ case 'h4':
51
+ return <h4>{message}</h4>;
52
+ case 'h5':
53
+ return <h5>{message}</h5>;
54
+ case 'h6':
55
+ return <h6>{message}</h6>;
56
+ case 'p':
57
+ return <p>{message}</p>;
58
+ case 'span':
59
+ return <span>{message}</span>;
60
+ default:
61
+ return <div>{message}</div>;
62
+ }
63
+ })()}
64
+ </div>
65
+ </section>
66
+ )
64
67
  );
65
68
  };
66
69
 
67
- export default ErrorMessage;
70
+ export default ErrorMessage;
@@ -27,6 +27,9 @@ type BaseErrorBoundaryState = {
27
27
  referenceId: string;
28
28
  };
29
29
 
30
+ /** Cap on the ring buffer of error reports exposed to genesis-telemetry.js. */
31
+ const MAX_ERROR_BOUNDARY_REPORTS = 20;
32
+
30
33
  const initialBoundaryState: BaseErrorBoundaryState = {
31
34
  hasError: false,
32
35
  error: null,
@@ -53,6 +56,7 @@ const toError = (value: unknown): Error => {
53
56
 
54
57
  const createReferenceId = (): string => {
55
58
  const timestamp = new Date().toISOString().split(':').join('-');
59
+ // oxlint-disable-next-line no-magic-numbers -- base-36 encoding with 6-char slice is a well-known ID generation pattern
56
60
  const random = Math.random().toString(36).slice(2, 8).toUpperCase();
57
61
  return `APP-${timestamp}-${random}`;
58
62
  };
@@ -68,6 +72,8 @@ const ErrorBoundaryFallback: React.FC<ErrorBoundaryFallbackProps> = ({
68
72
  const [copied, setCopied] = useState(false);
69
73
  const [copyHelpVisible, setCopyHelpVisible] = useState(false);
70
74
 
75
+ const COPY_RESET_DELAY_MS = 2_000;
76
+
71
77
  const handleCopy = async () => {
72
78
  try {
73
79
  await navigator.clipboard.writeText(details);
@@ -75,7 +81,7 @@ const ErrorBoundaryFallback: React.FC<ErrorBoundaryFallbackProps> = ({
75
81
  setCopyHelpVisible(false);
76
82
  window.setTimeout(() => {
77
83
  setCopied(false);
78
- }, 2_000);
84
+ }, COPY_RESET_DELAY_MS);
79
85
  } catch {
80
86
  setCopyHelpVisible(true);
81
87
  }
@@ -155,8 +161,13 @@ class BaseErrorBoundary extends React.Component<BaseErrorBoundaryProps, BaseErro
155
161
 
156
162
  // Expose to genesis-telemetry.js so the UI Builder agent can read it via get_preview_diagnostics
157
163
  const reports: unknown[] = ((window as any).__GENESIS_ERROR_BOUNDARY_REPORTS__ ??= []);
158
- reports.push({ message: normalized.message, stack: normalized.stack ?? null, componentStack: errorInfo.componentStack ?? null, ts: Date.now() });
159
- if (reports.length > 20) reports.shift();
164
+ reports.push({
165
+ message: normalized.message,
166
+ stack: normalized.stack ?? null,
167
+ componentStack: errorInfo.componentStack ?? null,
168
+ ts: Date.now(),
169
+ });
170
+ if (reports.length > MAX_ERROR_BOUNDARY_REPORTS) reports.shift();
160
171
  }
161
172
 
162
173
  private readonly handleRetry = (): void => {
@@ -186,9 +197,7 @@ class BaseErrorBoundary extends React.Component<BaseErrorBoundaryProps, BaseErro
186
197
  ].join('\n');
187
198
 
188
199
  const fallbackTitle =
189
- scope === 'application'
190
- ? 'Something went wrong'
191
- : `Something went wrong in "${title}"`;
200
+ scope === 'application' ? 'Something went wrong' : `Something went wrong in "${title}"`;
192
201
  const fallbackSubtitle =
193
202
  scope === 'application'
194
203
  ? 'The app hit an unexpected error. You can retry or copy diagnostics for a fast fix.'
@@ -2,10 +2,10 @@ import { renderAppRoutes } from '@genesislcap/foundation-react-utils/router';
2
2
  import DefaultLayout from '../../layouts/default/DefaultLayout';
3
3
  import NotFoundPage from '../../pages/NotFoundPage/NotFoundPage.tsx';
4
4
  import { useRoutesContext } from '../../store/RoutesContext';
5
- import ProtectedRoute from './ProtectedRoute';
6
5
  {{#if routes.[0]}}
7
6
  import SingleComponent, { initialComponentName } from '../single-component/SingleComponent';
8
7
  {{/if}}
8
+ import ProtectedRoute from './ProtectedRoute';
9
9
 
10
10
  /**
11
11
  * Builds the app's `<Routes>` from the declarative route table (see RoutesContext)
@@ -1,7 +1,11 @@
1
1
  import { createComponentRegistry } from '@genesislcap/foundation-react-utils/router';
2
+ {{#if routes.[0]}}
3
+ /* eslint-disable import-es/order -- generated route pages follow route-table order, not alphabetical */
2
4
  {{#each routes}}
3
5
  import {{pascalCase this.name}} from '../../pages/{{pascalCase this.name}}/{{pascalCase this.name}}';
4
6
  {{/each}}
7
+ /* eslint-enable import-es/order */
8
+ {{/if}}
5
9
 
6
10
  /**
7
11
  * Registry of components that can be rendered standalone via the
@@ -1,8 +1,10 @@
1
1
  import React, { DOMAttributes } from 'react';
2
2
 
3
- type CustomElement<T = React.HTMLAttributes<HTMLElement>> = Partial<T & DOMAttributes<T> & { [key: string]: any }>;
3
+ type CustomElement<T = React.HTMLAttributes<HTMLElement>> = Partial<
4
+ T & DOMAttributes<T> & { [key: string]: any }
5
+ >;
4
6
 
5
- declare module "react/jsx-runtime" {
7
+ declare module 'react/jsx-runtime' {
6
8
  namespace JSX {
7
9
  interface IntrinsicElements {
8
10
  'rapid-design-system-provider': CustomElement;
@@ -8,4 +8,4 @@ const hasPermissionHelper = (permissionCode: string | undefined): boolean => {
8
8
  return getUser().hasPermission(permissionCode);
9
9
  };
10
10
 
11
- export default hasPermissionHelper;
11
+ export default hasPermissionHelper;
@@ -11,4 +11,4 @@ const isAuthenticatedHelper = (): boolean => {
11
11
  return true;
12
12
  };
13
13
 
14
- export default isAuthenticatedHelper;
14
+ export default isAuthenticatedHelper;
@@ -5,12 +5,12 @@ const isConnectedHelper = (): Promise<boolean> => {
5
5
  const hostUrl = sessionStorage.getItem('hostUrl');
6
6
 
7
7
  if (connect.isConnected) {
8
- return new Promise((resolve) => resolve(true));
8
+ return Promise.resolve(true);
9
9
  }
10
10
  if (!hostUrl) {
11
- return new Promise((resolve) => resolve(false));
11
+ return Promise.resolve(false);
12
12
  }
13
13
  return connect.connect(hostUrl);
14
14
  };
15
15
 
16
- export default isConnectedHelper;
16
+ export default isConnectedHelper;