@finos/legend-application-studio 28.21.4 → 28.21.6

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 (100) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +4 -1
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +3 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +2 -1
  6. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioTelemetryHelper.js +11 -3
  8. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  9. package/lib/__lib__/LegendStudioUserDataHelper.d.ts +41 -1
  10. package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendStudioUserDataHelper.js +120 -1
  12. package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
  13. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
  14. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
  15. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
  16. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
  17. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
  18. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  19. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +13 -35
  20. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  21. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  22. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +20 -7
  23. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  24. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  25. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +59 -22
  26. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  27. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
  28. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
  29. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
  30. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  31. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +2 -2
  32. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  33. package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
  34. package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
  35. package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
  36. package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts +22 -0
  37. package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts.map +1 -0
  38. package/lib/components/workspace-setup/RecentWorkspacesPanel.js +80 -0
  39. package/lib/components/workspace-setup/RecentWorkspacesPanel.js.map +1 -0
  40. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  41. package/lib/components/workspace-setup/WorkspaceSetup.js +61 -6
  42. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/package.json +1 -1
  46. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  47. package/lib/stores/editor/EditorStore.js +31 -0
  48. package/lib/stores/editor/EditorStore.js.map +1 -1
  49. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +1 -1
  50. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  51. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +20 -48
  52. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  53. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +9 -14
  54. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  55. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +125 -78
  56. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  57. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
  58. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  59. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +216 -53
  60. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  61. package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts.map +1 -1
  62. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js +11 -0
  63. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js.map +1 -1
  64. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.d.ts.map +1 -1
  65. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js +11 -0
  66. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js.map +1 -1
  67. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
  68. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  69. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +57 -1
  70. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  71. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
  72. package/lib/stores/project-reviewer/ProjectReviewerStore.js +12 -0
  73. package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
  74. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +17 -0
  75. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  76. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +61 -0
  77. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  78. package/package.json +16 -16
  79. package/src/__lib__/LegendStudioEvent.ts +3 -0
  80. package/src/__lib__/LegendStudioTelemetryHelper.ts +35 -11
  81. package/src/__lib__/LegendStudioUserDataHelper.ts +204 -1
  82. package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +4 -0
  83. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +209 -187
  84. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +26 -7
  85. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +149 -86
  86. package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
  87. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +3 -11
  88. package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
  89. package/src/components/workspace-setup/RecentWorkspacesPanel.tsx +161 -0
  90. package/src/components/workspace-setup/WorkspaceSetup.tsx +97 -8
  91. package/src/stores/editor/EditorStore.ts +44 -0
  92. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +28 -50
  93. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +164 -100
  94. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +307 -72
  95. package/src/stores/editor/sidebar-state/ProjectOverviewState.ts +14 -0
  96. package/src/stores/editor/sidebar-state/WorkspaceReviewState.ts +14 -0
  97. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +84 -1
  98. package/src/stores/project-reviewer/ProjectReviewerStore.ts +15 -0
  99. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +93 -0
  100. package/tsconfig.json +1 -0
@@ -20,6 +20,7 @@ import {
20
20
  assertNonNullable,
21
21
  assertTrue,
22
22
  type GeneratorFn,
23
+ type PlainObject,
23
24
  } from '@finos/legend-shared';
24
25
  import type { EditorStore } from '../../EditorStore.js';
25
26
  import { action, flow, makeObservable, observable } from 'mobx';
@@ -27,14 +28,24 @@ import {
27
28
  type DeployProjectResponse,
28
29
  MetadataRequestOptions,
29
30
  } from '@finos/legend-graph';
31
+ import type { Entity } from '@finos/legend-storage';
30
32
  import { LegendStudioTelemetryHelper } from '../../../../__lib__/LegendStudioTelemetryHelper.js';
31
33
 
34
+ export const DEV_SNAPSHOT_VERSION = '1.0.0-SNAPSHOT';
35
+
32
36
  export class DevMetadataState {
33
37
  readonly editorStore: EditorStore;
34
38
  result: DeployProjectResponse | undefined;
35
39
  options: MetadataRequestOptions = new MetadataRequestOptions();
36
40
  pushState = ActionState.create();
37
41
 
42
+ // Compare-with-dev state
43
+ compareState = ActionState.create();
44
+ currentWorkspaceCode: string | undefined;
45
+ snapshotCode: string | undefined;
46
+ snapshotNotAvailable = false;
47
+ isCompareModalOpen = false;
48
+
38
49
  constructor(editorStore: EditorStore) {
39
50
  this.editorStore = editorStore;
40
51
 
@@ -43,6 +54,14 @@ export class DevMetadataState {
43
54
  push: flow,
44
55
  options: observable,
45
56
  setOptions: action,
57
+ compareState: observable,
58
+ currentWorkspaceCode: observable,
59
+ snapshotCode: observable,
60
+ snapshotNotAvailable: observable,
61
+ isCompareModalOpen: observable,
62
+ openCompareModal: action,
63
+ closeCompareModal: action,
64
+ compareWithSnapshot: flow,
46
65
  });
47
66
  }
48
67
 
@@ -50,6 +69,14 @@ export class DevMetadataState {
50
69
  this.options = options;
51
70
  }
52
71
 
72
+ openCompareModal(): void {
73
+ this.isCompareModalOpen = true;
74
+ }
75
+
76
+ closeCompareModal(): void {
77
+ this.isCompareModalOpen = false;
78
+ }
79
+
53
80
  get projectGAV(): { groupId: string; artifactId: string } | undefined {
54
81
  const currentProjectConfiguration =
55
82
  this.editorStore.projectConfigurationEditorState.projectConfiguration;
@@ -62,6 +89,55 @@ export class DevMetadataState {
62
89
  return undefined;
63
90
  }
64
91
 
92
+ *compareWithSnapshot(): GeneratorFn<void> {
93
+ try {
94
+ this.compareState.inProgress();
95
+ this.snapshotNotAvailable = false;
96
+ this.currentWorkspaceCode = undefined;
97
+ this.snapshotCode = undefined;
98
+ const gav = this.projectGAV;
99
+ assertNonNullable(gav, 'Project configuration is required to compare');
100
+ const graphManager = this.editorStore.graphManagerState.graphManager;
101
+
102
+ // 1) Current workspace -> Pure code (same as toggling to text mode)
103
+ const currentCode = (yield graphManager.graphToPureCode(
104
+ this.editorStore.graphManagerState.graph,
105
+ { pretty: true, excludeUnknown: true },
106
+ )) as string;
107
+ this.currentWorkspaceCode = currentCode;
108
+
109
+ // 2) Snapshot from depot -> Pure code; if the call fails we treat
110
+ // it as "nothing deployed yet"
111
+ let snapshotEntities: Entity[] | undefined;
112
+ try {
113
+ const entitiesJson =
114
+ (yield this.editorStore.depotServerClient.getVersionEntities(
115
+ gav.groupId,
116
+ gav.artifactId,
117
+ DEV_SNAPSHOT_VERSION,
118
+ )) as PlainObject<Entity>[];
119
+ snapshotEntities = entitiesJson as unknown as Entity[];
120
+ } catch {
121
+ this.snapshotNotAvailable = true;
122
+ this.compareState.complete();
123
+ return;
124
+ }
125
+
126
+ const snapshotCode = (yield graphManager.entitiesToPureCode(
127
+ snapshotEntities,
128
+ { pretty: true },
129
+ )) as string;
130
+ this.snapshotCode = snapshotCode;
131
+ this.compareState.complete();
132
+ } catch (error) {
133
+ assertErrorThrown(error);
134
+ this.editorStore.applicationStore.notificationService.notifyError(
135
+ `Error comparing with dev snapshot: ${error.message}`,
136
+ );
137
+ this.compareState.fail();
138
+ }
139
+ }
140
+
65
141
  *push(): GeneratorFn<void> {
66
142
  try {
67
143
  this.result = undefined;
@@ -80,6 +156,13 @@ export class DevMetadataState {
80
156
  'Project Name required to push to dev mode',
81
157
  );
82
158
  this.pushState.inProgress();
159
+ LegendStudioTelemetryHelper.logEvent_DevMetadataPushLaunched(
160
+ this.editorStore.applicationStore.telemetryService,
161
+ this.editorStore.editorMode.getSourceInfo(),
162
+ currentProjectConfiguration.groupId,
163
+ currentProjectConfiguration.artifactId,
164
+ undefined,
165
+ );
83
166
  const result =
84
167
  (yield this.editorStore.graphManagerState.graphManager.pushToDevMetadata(
85
168
  currentProjectConfiguration.groupId,
@@ -89,7 +172,7 @@ export class DevMetadataState {
89
172
  this.editorStore.graphManagerState.graph,
90
173
  )) as DeployProjectResponse;
91
174
  this.result = result;
92
- LegendStudioTelemetryHelper.logEvent_DevMetadataPushLaunched(
175
+ LegendStudioTelemetryHelper.logEvent_DevMetadataPushSucceeded(
93
176
  this.editorStore.applicationStore.telemetryService,
94
177
  this.editorStore.editorMode.getSourceInfo(),
95
178
  currentProjectConfiguration.groupId,
@@ -45,6 +45,7 @@ import {
45
45
  ReviewApproval,
46
46
  } from '@finos/legend-server-sdlc';
47
47
  import { LEGEND_STUDIO_APP_EVENT } from '../../__lib__/LegendStudioEvent.js';
48
+ import { LegendStudioUserDataHelper } from '../../__lib__/LegendStudioUserDataHelper.js';
48
49
  import { DEFAULT_TAB_SIZE } from '@finos/legend-application';
49
50
  import type { Entity } from '@finos/legend-storage';
50
51
  import { EntityDiffViewState } from '../editor/editor-state/entity-diff-editor-state/EntityDiffViewState.js';
@@ -457,6 +458,20 @@ export class ProjectReviewerStore {
457
458
  { message: `${this.review.title} [review]` },
458
459
  )) as PlainObject<Review>,
459
460
  );
461
+ // Committing a review deletes its workspace on SDLC. Drop the
462
+ // matching entry from the local recents cache (no-op if it wasn't
463
+ // there, e.g., the reviewer isn't the workspace author). Patch
464
+ // workspaces are never cached.
465
+ if (this.patchReleaseVersionId === undefined) {
466
+ LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(
467
+ this.editorStore.applicationStore.userDataService,
468
+ {
469
+ projectId: this.projectId,
470
+ workspaceId: this.review.workspaceId,
471
+ workspaceType: this.review.workspaceType,
472
+ },
473
+ );
474
+ }
460
475
  } catch (error) {
461
476
  assertErrorThrown(error);
462
477
  this.editorStore.applicationStore.logService.error(
@@ -51,6 +51,11 @@ import {
51
51
  ProjectConfigurationStatus,
52
52
  } from './ProjectConfigurationStatus.js';
53
53
  import { GraphManagerState } from '@finos/legend-graph';
54
+ import {
55
+ LegendStudioUserDataHelper,
56
+ type RecentProjectEntry,
57
+ type RecentWorkspaceEntry,
58
+ } from '../../__lib__/LegendStudioUserDataHelper.js';
54
59
 
55
60
  interface ImportProjectSuccessReport {
56
61
  projectId: string;
@@ -92,6 +97,12 @@ export class WorkspaceSetupStore {
92
97
 
93
98
  graphManagerState: GraphManagerState;
94
99
 
100
+ // Cached recents to make re-opening a project/workspace instantaneous.
101
+ // NOTE: patch-based workspaces are intentionally excluded from this cache.
102
+ recentProjects: RecentProjectEntry[] = [];
103
+ recentWorkspaces: RecentWorkspaceEntry[] = [];
104
+ selectRecentProjectState = ActionState.create();
105
+
95
106
  constructor(
96
107
  applicationStore: LegendStudioApplicationStore,
97
108
  sdlcServerClient: SDLCServerClient,
@@ -113,6 +124,8 @@ export class WorkspaceSetupStore {
113
124
  enginePromise: observable,
114
125
  sandboxModal: observable,
115
126
  hasSandboxAccess: observable,
127
+ recentProjects: observable,
128
+ recentWorkspaces: observable,
116
129
  setShowCreateProjectModal: action,
117
130
  setShowCreateWorkspaceModal: action,
118
131
  setShowAdvancedWorkspaceFilterOptions: action,
@@ -121,6 +134,9 @@ export class WorkspaceSetupStore {
121
134
  changeWorkspace: action,
122
135
  resetProject: action,
123
136
  resetWorkspace: action,
137
+ removeRecentProject: action,
138
+ removeRecentWorkspace: action,
139
+ clearRecents: action,
124
140
  initialize: flow,
125
141
  loadProjects: flow,
126
142
  loadSandboxProject: flow,
@@ -130,6 +146,7 @@ export class WorkspaceSetupStore {
130
146
  createSandboxProject: flow,
131
147
  createWorkspace: flow,
132
148
  initializeEngine: flow,
149
+ selectRecentProject: flow,
133
150
  });
134
151
 
135
152
  this.applicationStore = applicationStore;
@@ -138,6 +155,14 @@ export class WorkspaceSetupStore {
138
155
  applicationStore.pluginManager,
139
156
  applicationStore.logService,
140
157
  );
158
+ this.recentProjects =
159
+ LegendStudioUserDataHelper.workspaceSetup_getRecentProjects(
160
+ applicationStore.userDataService,
161
+ );
162
+ this.recentWorkspaces =
163
+ LegendStudioUserDataHelper.workspaceSetup_getRecentWorkspaces(
164
+ applicationStore.userDataService,
165
+ );
141
166
  if (this.supportsCreatingSandboxProject) {
142
167
  flowResult(this.initializeEngine()).catch(
143
168
  applicationStore.alertUnhandledError,
@@ -197,6 +222,67 @@ export class WorkspaceSetupStore {
197
222
  this.sandboxModal = val;
198
223
  }
199
224
 
225
+ // --- Recents -------------------------------------------------------------
226
+ // NOTE: writes to the recents cache happen from the editor (see
227
+ // `EditorStore.initialize`) at the moment a workspace is actually opened.
228
+ // This store only reads the cache (to seed dropdowns) and prunes entries
229
+ // that are discovered to be stale.
230
+
231
+ removeRecentProject(projectId: string): void {
232
+ const updated =
233
+ LegendStudioUserDataHelper.workspaceSetup_removeRecentProject(
234
+ this.applicationStore.userDataService,
235
+ projectId,
236
+ );
237
+ this.recentProjects = updated.projects;
238
+ this.recentWorkspaces = updated.workspaces;
239
+ }
240
+
241
+ removeRecentWorkspace(entry: {
242
+ projectId: string;
243
+ workspaceId: string;
244
+ workspaceType: WorkspaceType;
245
+ }): void {
246
+ this.recentWorkspaces =
247
+ LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(
248
+ this.applicationStore.userDataService,
249
+ entry,
250
+ );
251
+ }
252
+
253
+ clearRecents(): void {
254
+ LegendStudioUserDataHelper.workspaceSetup_clearRecents(
255
+ this.applicationStore.userDataService,
256
+ );
257
+ this.recentProjects = [];
258
+ this.recentWorkspaces = [];
259
+ }
260
+
261
+ /**
262
+ * Fetches a project by id (used when the user picks a cached "recent"
263
+ * project that may not be in the current search results) and switches to
264
+ * it. If the project no longer exists, the entry is pruned from recents.
265
+ */
266
+ *selectRecentProject(projectId: string): GeneratorFn<void> {
267
+ this.selectRecentProjectState.inProgress();
268
+ try {
269
+ const project = Project.serialization.fromJson(
270
+ (yield this.sdlcServerClient.getProject(
271
+ projectId,
272
+ )) as PlainObject<Project>,
273
+ );
274
+ yield flowResult(this.changeProject(project));
275
+ this.selectRecentProjectState.pass();
276
+ } catch (error) {
277
+ assertErrorThrown(error);
278
+ this.removeRecentProject(projectId);
279
+ this.applicationStore.notificationService.notifyWarning(
280
+ `Recent project could not be opened and was removed from recents`,
281
+ );
282
+ this.selectRecentProjectState.fail();
283
+ }
284
+ }
285
+
200
286
  *createSandboxProject(): GeneratorFn<void> {
201
287
  try {
202
288
  if (!this.hasSandboxAccess) {
@@ -277,6 +363,7 @@ export class WorkspaceSetupStore {
277
363
  )) as PlainObject<Project>,
278
364
  );
279
365
  } catch {
366
+ this.removeRecentProject(projectId);
280
367
  this.applicationStore.navigationService.navigator.updateCurrentLocation(
281
368
  generateSetupRoute(undefined, undefined),
282
369
  );
@@ -513,6 +600,12 @@ export class WorkspaceSetupStore {
513
600
  if (matchingWorkspace) {
514
601
  this.changeWorkspace(matchingWorkspace);
515
602
  } else {
603
+ // Workspace no longer exists — prune from recents.
604
+ this.removeRecentWorkspace({
605
+ projectId: project.projectId,
606
+ workspaceId: workspaceInfo.workspaceId,
607
+ workspaceType: workspaceInfo.workspaceType,
608
+ });
516
609
  this.applicationStore.navigationService.navigator.updateCurrentLocation(
517
610
  generateSetupRoute(project.projectId, undefined),
518
611
  );
package/tsconfig.json CHANGED
@@ -374,6 +374,7 @@
374
374
  "./src/components/workspace-setup/CreateProjectModal.tsx",
375
375
  "./src/components/workspace-setup/CreateWorkspaceModal.tsx",
376
376
  "./src/components/workspace-setup/ProjectSelectorUtils.tsx",
377
+ "./src/components/workspace-setup/RecentWorkspacesPanel.tsx",
377
378
  "./src/components/workspace-setup/WorkspaceSelectorUtils.tsx",
378
379
  "./src/components/workspace-setup/WorkspaceSetup.tsx"
379
380
  ],