@finos/legend-application-studio 28.21.11 → 28.21.13

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 (79) hide show
  1. package/lib/components/editor/ActivityBar.d.ts +13 -0
  2. package/lib/components/editor/ActivityBar.d.ts.map +1 -1
  3. package/lib/components/editor/ActivityBar.js +59 -4
  4. package/lib/components/editor/ActivityBar.js.map +1 -1
  5. package/lib/components/editor/command/project-search.css +1 -1
  6. package/lib/components/editor/command/project-search.css.map +1 -1
  7. package/lib/components/editor/editor-group/GrammarTextEditor.d.ts.map +1 -1
  8. package/lib/components/editor/editor-group/GrammarTextEditor.js +6 -2
  9. package/lib/components/editor/editor-group/GrammarTextEditor.js.map +1 -1
  10. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts +2 -0
  11. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  12. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +81 -19
  13. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  14. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.d.ts.map +1 -1
  15. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js +10 -1
  16. package/lib/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.js.map +1 -1
  17. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.d.ts.map +1 -1
  18. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js +3 -10
  19. package/lib/components/editor/editor-group/database-editor/DatabaseEditor.js.map +1 -1
  20. package/lib/components/editor/editor-group/diff-editor/EntityChangeConflictEditor.d.ts.map +1 -1
  21. package/lib/components/editor/editor-group/diff-editor/EntityChangeConflictEditor.js +8 -3
  22. package/lib/components/editor/editor-group/diff-editor/EntityChangeConflictEditor.js.map +1 -1
  23. package/lib/components/editor/editor-group/function-activator/FunctionEditor.js +1 -1
  24. package/lib/components/editor/editor-group/function-activator/FunctionEditor.js.map +1 -1
  25. package/lib/components/editor/editor-group/mapping-editor/MappingEditor.js +1 -1
  26. package/lib/components/editor/editor-group/mapping-editor/MappingEditor.js.map +1 -1
  27. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  28. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -1
  29. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  30. package/lib/components/editor/editor-group/uml-editor/StereotypeSelector.d.ts.map +1 -1
  31. package/lib/components/editor/editor-group/uml-editor/StereotypeSelector.js +1 -3
  32. package/lib/components/editor/editor-group/uml-editor/StereotypeSelector.js.map +1 -1
  33. package/lib/components/editor/editor-group/uml-editor/TaggedValueEditor.d.ts.map +1 -1
  34. package/lib/components/editor/editor-group/uml-editor/TaggedValueEditor.js +1 -3
  35. package/lib/components/editor/editor-group/uml-editor/TaggedValueEditor.js.map +1 -1
  36. package/lib/components/editor/ingest-definition-editor.css +1 -1
  37. package/lib/components/editor/ingest-definition-editor.css.map +1 -1
  38. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  39. package/lib/components/workspace-setup/WorkspaceSetup.js +20 -3
  40. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  41. package/lib/index.css +2 -2
  42. package/lib/index.css.map +1 -1
  43. package/lib/index.d.ts +1 -0
  44. package/lib/index.d.ts.map +1 -1
  45. package/lib/index.js +1 -0
  46. package/lib/index.js.map +1 -1
  47. package/lib/package.json +1 -1
  48. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts +0 -9
  49. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.d.ts.map +1 -1
  50. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js +0 -34
  51. package/lib/stores/editor/editor-state/element-editor-state/DatabaseEditorState.js.map +1 -1
  52. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts +61 -0
  53. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -0
  54. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js +17 -0
  55. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js.map +1 -0
  56. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts +2 -2
  57. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -1
  58. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  59. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +6 -2
  60. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  61. package/package.json +12 -12
  62. package/src/components/editor/ActivityBar.tsx +88 -0
  63. package/src/components/editor/editor-group/GrammarTextEditor.tsx +8 -2
  64. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +207 -29
  65. package/src/components/editor/editor-group/database-editor/DatabaseDiagramCanvas.tsx +11 -0
  66. package/src/components/editor/editor-group/database-editor/DatabaseEditor.tsx +1 -28
  67. package/src/components/editor/editor-group/diff-editor/EntityChangeConflictEditor.tsx +10 -3
  68. package/src/components/editor/editor-group/function-activator/FunctionEditor.tsx +1 -1
  69. package/src/components/editor/editor-group/mapping-editor/MappingEditor.tsx +1 -1
  70. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +5 -1
  71. package/src/components/editor/editor-group/uml-editor/StereotypeSelector.tsx +1 -5
  72. package/src/components/editor/editor-group/uml-editor/TaggedValueEditor.tsx +1 -5
  73. package/src/components/workspace-setup/WorkspaceSetup.tsx +27 -2
  74. package/src/index.ts +1 -0
  75. package/src/stores/editor/editor-state/element-editor-state/DatabaseEditorState.ts +0 -42
  76. package/src/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.ts +77 -0
  77. package/src/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.ts +1 -2
  78. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +5 -0
  79. package/tsconfig.json +1 -0
@@ -80,6 +80,7 @@ import {
80
80
  PanelLoadingIndicator,
81
81
  GearSuggestIcon,
82
82
  FlaskIcon,
83
+ SparkleIcon,
83
84
  } from '@finos/legend-art';
84
85
  import {
85
86
  type ChangeEventHandler,
@@ -106,7 +107,7 @@ import {
106
107
  QueryBuilderAdvancedWorkflowState,
107
108
  type QueryBuilderState,
108
109
  } from '@finos/legend-query-builder';
109
- import { action, autorun, flowResult } from 'mobx';
110
+ import { action, autorun, flowResult, runInAction } from 'mobx';
110
111
  import { DataProductTestableEditor } from './testable/DataProductTestableEditor.js';
111
112
  import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
112
113
  import { CodeEditor } from '@finos/legend-lego/code-editor';
@@ -202,6 +203,11 @@ import {
202
203
  import type { LegendStudioApplicationStore } from '../../../../stores/LegendStudioBaseStore.js';
203
204
  import type { DepotServerClient } from '@finos/legend-server-depot';
204
205
  import { RelationElementEditor } from '../data-editor/RelationElementsDataEditor.js';
206
+ import type {
207
+ AccessPointMeta,
208
+ DataProductDocResponse,
209
+ DSL_DataProduct_LegendStudioApplicationPlugin_Extension,
210
+ } from '../../../../stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
205
211
 
206
212
  export enum AP_GROUP_MODAL_ERRORS {
207
213
  GROUP_NAME_EMPTY = 'Group Name is empty',
@@ -262,8 +268,11 @@ const hoverIcon = () => {
262
268
  };
263
269
 
264
270
  const AccessPointTitle = observer(
265
- (props: { accessPointState: LakehouseAccessPointState }) => {
266
- const { accessPointState } = props;
271
+ (props: {
272
+ accessPointState: LakehouseAccessPointState;
273
+ aiSuggestionMeta?: AccessPointMeta | undefined;
274
+ }) => {
275
+ const { accessPointState, aiSuggestionMeta } = props;
267
276
  const accessPoint = accessPointState.accessPoint;
268
277
  const [editingName, setEditingName] = useState(
269
278
  accessPoint.id === newNamePlaceholder,
@@ -286,29 +295,37 @@ const AccessPointTitle = observer(
286
295
  }
287
296
  });
288
297
 
289
- return editingName ? (
290
- <textarea
291
- className="access-point-editor__name"
292
- spellCheck={false}
293
- value={accessPoint.id}
294
- onChange={updateAccessPointName}
295
- placeholder={'Access Point Name'}
296
- onBlur={handleNameBlur}
297
- style={{
298
- borderColor:
299
- accessPoint.id === newNamePlaceholder
300
- ? 'var(--color-red-300)'
301
- : 'transparent',
302
- }}
303
- />
304
- ) : (
305
- <div
306
- className="access-point-editor__name__label"
307
- onClick={handleNameEdit}
308
- title="Click to edit access point name"
309
- style={{ flex: '1 1 auto' }}
310
- >
311
- {accessPoint.id}
298
+ return (
299
+ <div style={{ flex: '1 1 auto', minWidth: 0 }}>
300
+ {editingName ? (
301
+ <textarea
302
+ className="access-point-editor__name"
303
+ spellCheck={false}
304
+ value={accessPoint.id}
305
+ onChange={updateAccessPointName}
306
+ placeholder={'Access Point Name'}
307
+ onBlur={handleNameBlur}
308
+ style={{
309
+ borderColor:
310
+ accessPoint.id === newNamePlaceholder
311
+ ? 'var(--color-red-300)'
312
+ : 'transparent',
313
+ }}
314
+ />
315
+ ) : (
316
+ <div
317
+ className="access-point-editor__name__label"
318
+ onClick={handleNameEdit}
319
+ title="Click to edit access point name"
320
+ >
321
+ {accessPoint.id}
322
+ </div>
323
+ )}
324
+ {aiSuggestionMeta && (
325
+ <div className="data-product-editor__ai-suggestion-inline">
326
+ {aiSuggestionMeta.name}
327
+ </div>
328
+ )}
312
329
  </div>
313
330
  );
314
331
  },
@@ -697,8 +714,9 @@ export const LakehouseDataProductAccessPointEditor = observer(
697
714
  (props: {
698
715
  accessPointState: LakehouseAccessPointState;
699
716
  isReadOnly: boolean;
717
+ aiSuggestionMeta?: AccessPointMeta | undefined;
700
718
  }) => {
701
- const { accessPointState } = props;
719
+ const { accessPointState, aiSuggestionMeta } = props;
702
720
  const editorStore = useEditorStore();
703
721
  const accessPoint = accessPointState.accessPoint;
704
722
  const groupState = accessPointState.state;
@@ -925,7 +943,10 @@ export const LakehouseDataProductAccessPointEditor = observer(
925
943
  />
926
944
  <div style={{ flex: 1 }}>
927
945
  <div className="access-point-editor__metadata">
928
- <AccessPointTitle accessPointState={accessPointState} />
946
+ <AccessPointTitle
947
+ accessPointState={accessPointState}
948
+ aiSuggestionMeta={aiSuggestionMeta}
949
+ />
929
950
  <div className="access-point-editor__info">
930
951
  <div className="access-point-editor__reproducible">
931
952
  <Checkbox
@@ -1166,6 +1187,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
1166
1187
  {isHoveringTitle && hoverIcon()}
1167
1188
  </div>
1168
1189
  )}
1190
+ {aiSuggestionMeta && (
1191
+ <div className="data-product-editor__ai-suggestion-inline">
1192
+ {aiSuggestionMeta.title}
1193
+ </div>
1194
+ )}
1169
1195
  {editingDescription ? (
1170
1196
  <textarea
1171
1197
  className="panel__content__form__section__input"
@@ -1209,6 +1235,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
1209
1235
  {isHoveringDesc && hoverIcon()}
1210
1236
  </div>
1211
1237
  )}
1238
+ {aiSuggestionMeta && (
1239
+ <div className="data-product-editor__ai-suggestion-inline">
1240
+ {aiSuggestionMeta.description}
1241
+ </div>
1242
+ )}
1212
1243
  <div className="access-point-editor__content">
1213
1244
  <div className="access-point-editor__generic-entry">
1214
1245
  <div className="access-point-editor__entry__container">
@@ -1712,6 +1743,10 @@ const AccessPointGroupEditor = observer(
1712
1743
  const [isHoveringName, setIsHoveringName] = useState(false);
1713
1744
  const [editingTitle, setEditingTitle] = useState(false);
1714
1745
  const [isHoveringTitle, setIsHoveringTitle] = useState(false);
1746
+ const [isSuggestingWithAI, setIsSuggestingWithAI] = useState(false);
1747
+ const [aiSuggestion, setAISuggestion] = useState<
1748
+ DataProductDocResponse | undefined
1749
+ >(undefined);
1715
1750
  const handleDescriptionEdit = () => setEditingDescription(true);
1716
1751
  const handleDescriptionBlur = () => {
1717
1752
  setEditingDescription(false);
@@ -1767,6 +1802,96 @@ const AccessPointGroupEditor = observer(
1767
1802
  accessPointGroup_setTitle(groupState.value, val);
1768
1803
  };
1769
1804
 
1805
+ // AI suggestion for APG
1806
+ const legendAIUrl = editorStore.applicationStore.config.legendAIUrl;
1807
+ const aiDocSuggester = legendAIUrl
1808
+ ? editorStore.pluginManager
1809
+ .getApplicationPlugins()
1810
+ .map((p) =>
1811
+ (
1812
+ p as DSL_DataProduct_LegendStudioApplicationPlugin_Extension
1813
+ ).getExtraDataProductDocumentationAISuggester?.bind(p),
1814
+ )
1815
+ .find(Boolean)
1816
+ : undefined;
1817
+ const suggestWithAI = async (): Promise<void> => {
1818
+ if (!aiDocSuggester || !legendAIUrl) {
1819
+ return;
1820
+ }
1821
+ setIsSuggestingWithAI(true);
1822
+ setAISuggestion(undefined);
1823
+ try {
1824
+ const definitions =
1825
+ await editorStore.graphManagerState.graphManager.graphToPureCode(
1826
+ editorStore.graphManagerState.graph,
1827
+ );
1828
+ const product = productEditorState.product;
1829
+ const suggestion = await aiDocSuggester(
1830
+ { definitions, data_product_name: product.path },
1831
+ legendAIUrl,
1832
+ );
1833
+ runInAction(() => {
1834
+ setAISuggestion(suggestion);
1835
+ });
1836
+ } finally {
1837
+ runInAction(() => {
1838
+ setIsSuggestingWithAI(false);
1839
+ });
1840
+ }
1841
+ };
1842
+ const applyAISuggestion = (): void => {
1843
+ if (!aiSuggestion) {
1844
+ return;
1845
+ }
1846
+ // Find matching group in the response
1847
+ const gIdx =
1848
+ productEditorState.accessPointGroupStates.indexOf(groupState);
1849
+ const groupMeta =
1850
+ aiSuggestion.access_point_groups.find(
1851
+ (g) => g.name === groupState.value.id,
1852
+ ) ?? aiSuggestion.access_point_groups[gIdx];
1853
+ if (groupMeta) {
1854
+ accessPointGroup_setTitle(groupState.value, groupMeta.title);
1855
+ accessPointGroup_setDescription(
1856
+ groupState.value,
1857
+ groupMeta.description,
1858
+ );
1859
+ }
1860
+ // Apply access point metadata for this group
1861
+ const apMetas = aiSuggestion.access_points.filter(
1862
+ (ap) => ap.group === (groupMeta?.name ?? groupState.value.id),
1863
+ );
1864
+ for (
1865
+ let j = 0;
1866
+ j < apMetas.length && j < groupState.value.accessPoints.length;
1867
+ j++
1868
+ ) {
1869
+ const apMeta = apMetas[j] as (typeof apMetas)[number];
1870
+ const ap = groupState.value.accessPoints[
1871
+ j
1872
+ ] as (typeof groupState.value.accessPoints)[number];
1873
+ // Convert suggested name to valid identifier (alphanumeric + underscore)
1874
+ const sanitizedName = apMeta.name.replace(/[^0-9a-zA-Z_]/g, '_');
1875
+ if (sanitizedName) {
1876
+ ap.id = sanitizedName;
1877
+ }
1878
+ accessPoint_setTitle(ap, apMeta.title);
1879
+ accessPoint_setDescription(ap, apMeta.description);
1880
+ }
1881
+ setAISuggestion(undefined);
1882
+ };
1883
+
1884
+ // Computed AI suggestion for current group
1885
+ const groupIndex =
1886
+ productEditorState.accessPointGroupStates.indexOf(groupState);
1887
+ const aiGroupMeta =
1888
+ aiSuggestion?.access_point_groups.find(
1889
+ (g) => g.name === groupState.value.id,
1890
+ ) ?? aiSuggestion?.access_point_groups[groupIndex];
1891
+ const aiAccessPointMetas = aiSuggestion?.access_points.filter(
1892
+ (ap) => ap.group === (aiGroupMeta?.name ?? groupState.value.id),
1893
+ );
1894
+
1770
1895
  const handleRemoveAccessPointGroup = (): void => {
1771
1896
  editorStore.applicationStore.alertService.setActionAlertInfo({
1772
1897
  message:
@@ -1852,6 +1977,48 @@ const AccessPointGroupEditor = observer(
1852
1977
  <TimesIcon />
1853
1978
  </button>
1854
1979
  </div>
1980
+ {aiDocSuggester && (
1981
+ <div style={{ padding: '0.25rem 0.5rem' }}>
1982
+ <PanelLoadingIndicator isLoading={isSuggestingWithAI} />
1983
+ {aiSuggestion ? (
1984
+ <div className="data-product-editor__ai-suggestion__actions">
1985
+ <button
1986
+ className="btn btn--dark data-product-editor__ai-suggestion__apply-btn"
1987
+ onClick={applyAISuggestion}
1988
+ title="Apply AI-generated title, description, and access point metadata for this group"
1989
+ >
1990
+ Apply
1991
+ </button>
1992
+ <button
1993
+ className="btn data-product-editor__ai-suggestion__dismiss-btn"
1994
+ onClick={(): void => setAISuggestion(undefined)}
1995
+ >
1996
+ Dismiss
1997
+ </button>
1998
+ <span className="data-product-editor__ai-suggestion-badge">
1999
+ <SparkleIcon />
2000
+ AI Suggestion
2001
+ </span>
2002
+ </div>
2003
+ ) : (
2004
+ <button
2005
+ className="data-product-editor__ai-suggest-btn"
2006
+ onClick={(): void => {
2007
+ suggestWithAI().catch(
2008
+ editorStore.applicationStore.alertUnhandledError,
2009
+ );
2010
+ }}
2011
+ disabled={isSuggestingWithAI || isReadOnly}
2012
+ title="Use AI to suggest title and descriptions for this access point group"
2013
+ >
2014
+ <SparkleIcon />
2015
+ <span>
2016
+ {isSuggestingWithAI ? 'Suggesting...' : 'Suggest with AI'}
2017
+ </span>
2018
+ </button>
2019
+ )}
2020
+ </div>
2021
+ )}
1855
2022
  <div className="access-point-editor__group-container__name-editor">
1856
2023
  {editingTitle ? (
1857
2024
  <textarea
@@ -1896,6 +2063,11 @@ const AccessPointGroupEditor = observer(
1896
2063
  </div>
1897
2064
  )}
1898
2065
  </div>
2066
+ {aiGroupMeta && (
2067
+ <div className="data-product-editor__ai-suggestion-inline">
2068
+ {aiGroupMeta.title}
2069
+ </div>
2070
+ )}
1899
2071
  <div className="access-point-editor__group-container__description-editor">
1900
2072
  {editingDescription ? (
1901
2073
  <textarea
@@ -1938,6 +2110,11 @@ const AccessPointGroupEditor = observer(
1938
2110
  {isHoveringDescription && hoverIcon()}
1939
2111
  </div>
1940
2112
  )}
2113
+ {aiGroupMeta && (
2114
+ <div className="data-product-editor__ai-suggestion-inline">
2115
+ {aiGroupMeta.description}
2116
+ </div>
2117
+ )}
1941
2118
  </div>
1942
2119
  {editorStore.applicationStore.config.options.dataProductConfig && (
1943
2120
  <AccessPointGroupPublicToggle groupState={groupState} />
@@ -1975,11 +2152,12 @@ const AccessPointGroupEditor = observer(
1975
2152
  >
1976
2153
  {groupState.accessPointStates
1977
2154
  .filter(filterByType(LakehouseAccessPointState))
1978
- .map((apState) => (
2155
+ .map((apState, idx) => (
1979
2156
  <LakehouseDataProductAccessPointEditor
1980
2157
  key={apState.uuid}
1981
2158
  isReadOnly={isReadOnly}
1982
2159
  accessPointState={apState}
2160
+ aiSuggestionMeta={aiAccessPointMetas?.[idx]}
1983
2161
  />
1984
2162
  ))}
1985
2163
  </div>
@@ -58,6 +58,7 @@ import {
58
58
  resolveJoinFormula,
59
59
  } from './DatabaseDiagramHelper.js';
60
60
  import type { DatabaseEditorState } from '../../../../stores/editor/editor-state/element-editor-state/DatabaseEditorState.js';
61
+ import { useApplicationStore } from '@finos/legend-application';
61
62
 
62
63
  const NODE_TYPES = {
63
64
  table: DatabaseTableNode,
@@ -107,6 +108,7 @@ const findRelationById = (
107
108
  const DatabaseDiagramCanvasInner = observer(
108
109
  (props: { editorState: DatabaseEditorState }) => {
109
110
  const { editorState } = props;
111
+ const applicationStore = useApplicationStore();
110
112
  const {
111
113
  database,
112
114
  selectedRelation,
@@ -521,6 +523,15 @@ const DatabaseDiagramCanvasInner = observer(
521
523
  <div className="database-diagram__canvas-shell">
522
524
  <ReactFlow
523
525
  className="database-diagram__canvas"
526
+ // Drive ReactFlow's built-in light/dark variant from the active app
527
+ // theme. The chrome (canvas bg, controls, minimap, edges, dot pattern)
528
+ // then tracks studio's theme without us having to override individual
529
+ // `--xy-*` variables.
530
+ colorMode={
531
+ applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
532
+ ? 'light'
533
+ : 'dark'
534
+ }
524
535
  nodes={selectionAwareNodes}
525
536
  edges={selectionAwareEdges}
526
537
  nodeTypes={NODE_TYPES}
@@ -20,12 +20,10 @@ import {
20
20
  ChevronRightIcon,
21
21
  InfoCircleIcon,
22
22
  LockIcon,
23
- MoonIcon,
24
23
  ResizablePanel,
25
24
  ResizablePanelGroup,
26
25
  ResizablePanelSplitter,
27
26
  ResizablePanelSplitterLine,
28
- SunIcon,
29
27
  clsx,
30
28
  getCollapsiblePanelGroupProps,
31
29
  } from '@finos/legend-art';
@@ -90,14 +88,7 @@ export const DatabaseEditor = observer(() => {
90
88
  );
91
89
 
92
90
  return (
93
- <div
94
- className={clsx('database-editor', {
95
- // Local light-theme opt-in. Scoped to this editor only — the rest
96
- // of Studio remains in its configured theme. SCSS overrides hang
97
- // off this modifier class.
98
- 'database-editor--light': editorState.theme === 'light',
99
- })}
100
- >
91
+ <div className="database-editor">
101
92
  <div className="database-editor__tabs__header">
102
93
  <div className="database-editor__tabs">
103
94
  {TABS.map((tab) => (
@@ -128,24 +119,6 @@ export const DatabaseEditor = observer(() => {
128
119
  READ ONLY
129
120
  </span>
130
121
  </div>
131
- {/*
132
- * Light/dark theme toggle. Scoped to this editor only — see
133
- * `DatabaseEditorState.toggleTheme`. Icon switches to surface the
134
- * mode the user would jump TO if they clicked (current=dark shows
135
- * a sun, current=light shows a moon).
136
- */}
137
- <button
138
- type="button"
139
- className="database-editor__theme-toggle"
140
- onClick={() => editorState.toggleTheme()}
141
- title={
142
- editorState.theme === 'dark'
143
- ? 'Switch to light theme (this editor only)'
144
- : 'Switch to dark theme'
145
- }
146
- >
147
- {editorState.theme === 'dark' ? <SunIcon /> : <MoonIcon />}
148
- </button>
149
122
  </div>
150
123
  <div className="database-editor__content">
151
124
  {database instanceof INTERNAL__LakehouseGeneratedDatabase && (
@@ -54,7 +54,7 @@ import {
54
54
  getCodeEditorValue,
55
55
  normalizeLineEnding,
56
56
  clearMarkers,
57
- CODE_EDITOR_THEME,
57
+ getCodeEditorThemeForAppTheme,
58
58
  CODE_EDITOR_LANGUAGE,
59
59
  } from '@finos/legend-code-editor';
60
60
  import {
@@ -154,6 +154,7 @@ export const EntityChangeConflictSideBarItem = observer(
154
154
  const MergeConflictEditor = observer(
155
155
  (props: { conflictEditorState: EntityChangeConflictEditorState }) => {
156
156
  const { conflictEditorState } = props;
157
+ const applicationStore = useApplicationStore();
157
158
  const isReadOnly = conflictEditorState.isReadOnly;
158
159
  const [editor, setEditor] = useState<
159
160
  monacoEditorAPI.IStandaloneCodeEditor | undefined
@@ -189,7 +190,10 @@ const MergeConflictEditor = observer(
189
190
  const element = textInputRef.current;
190
191
  const _editor = monacoEditorAPI.create(element, {
191
192
  ...getBaseCodeEditorOptions(),
192
- theme: CODE_EDITOR_THEME.DEFAULT_DARK,
193
+ // Match the active app theme — see GrammarTextEditor for the rationale.
194
+ theme: getCodeEditorThemeForAppTheme(
195
+ applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled,
196
+ ),
193
197
  language: CODE_EDITOR_LANGUAGE.PURE,
194
198
  minimap: { enabled: false },
195
199
  formatOnType: true,
@@ -198,7 +202,10 @@ const MergeConflictEditor = observer(
198
202
  _editor.focus(); // focus on the editor initially so we can correctly compute next/prev conflict chunks
199
203
  setEditor(_editor);
200
204
  }
201
- }, [editor]);
205
+ }, [
206
+ editor,
207
+ applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled,
208
+ ]);
202
209
 
203
210
  if (editor) {
204
211
  // dispose the old editor content setter in case the `updateInput` handler changes
@@ -1521,7 +1521,7 @@ export const FunctionEditor = observer(() => {
1521
1521
  return (
1522
1522
  <div
1523
1523
  data-testid={LEGEND_STUDIO_TEST_ID.FUNCTION_EDITOR}
1524
- className="function-editor uml-editor uml-editor--dark"
1524
+ className="function-editor uml-editor"
1525
1525
  >
1526
1526
  <Panel>
1527
1527
  <div className="panel__header">
@@ -220,7 +220,7 @@ const MappingClassMappingEditor = observer(() => {
220
220
  );
221
221
 
222
222
  return (
223
- <div className={clsx('mapping-editor', 'mapping__theme__dark')}>
223
+ <div className="mapping-editor">
224
224
  <ResizablePanelGroup orientation="vertical">
225
225
  <ResizablePanel size={300} minSize={300}>
226
226
  <div className="mapping-editor__side-bar">
@@ -267,7 +267,11 @@ const ServiceGeneralEditor = observer(() => {
267
267
  setIsSuggestingWithAI(true);
268
268
  setAIDocSuggestion(undefined);
269
269
  try {
270
- const suggestion = await aiDocSuggester(service, legendAIUrl);
270
+ const serviceGrammar =
271
+ await editorStore.graphManagerState.graphManager.elementsToPureCode([
272
+ service,
273
+ ]);
274
+ const suggestion = await aiDocSuggester(serviceGrammar, legendAIUrl);
271
275
  setAIDocSuggestion(suggestion);
272
276
  } finally {
273
277
  setIsSuggestingWithAI(false);
@@ -185,11 +185,7 @@ export const StereotypeSelector = observer(
185
185
  dragSourceConnector={handleRef}
186
186
  isDragging={isBeingDragged}
187
187
  />
188
- <div
189
- className={clsx('stereotype-selector', {
190
- 'stereotype-selector--dark': darkTheme,
191
- })}
192
- >
188
+ <div className="stereotype-selector">
193
189
  <div className="stereotype-selector__profile">
194
190
  <CustomSelectorInput
195
191
  className="stereotype-selector__profile__selector"
@@ -194,11 +194,7 @@ export const TaggedValueEditor = observer(
194
194
  dragSourceConnector={handleRef}
195
195
  isDragging={isBeingDragged}
196
196
  />
197
- <div
198
- className={clsx('tagged-value-editor', {
199
- 'tagged-value-editor--dark': darkTheme,
200
- })}
201
- >
197
+ <div className="tagged-value-editor">
202
198
  <div className="tagged-value-editor__profile">
203
199
  <CustomSelectorInput
204
200
  className="tagged-value-editor__profile__selector"
@@ -49,7 +49,7 @@ import { useApplicationNavigationContext } from '@finos/legend-application';
49
49
  import { useParams } from '@finos/legend-application/browser';
50
50
  import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../__lib__/LegendStudioDocumentation.js';
51
51
  import { CreateProjectModal } from './CreateProjectModal.js';
52
- import { ActivityBarMenu } from '../editor/ActivityBar.js';
52
+ import { ActivityBarMenu, ColorThemeToggle } from '../editor/ActivityBar.js';
53
53
  import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../__lib__/LegendStudioApplicationNavigationContext.js';
54
54
  import { CreateWorkspaceModal } from './CreateWorkspaceModal.js';
55
55
  import { RecentWorkspacesPanel } from './RecentWorkspacesPanel.js';
@@ -369,14 +369,24 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
369
369
  // Build a unified option list: recent projects (that aren't already in the
370
370
  // loaded set) are prepended so users can instantly re-open common work
371
371
  // without waiting for the SDLC search to round-trip.
372
+ const sandboxProject =
373
+ setupStore.sandboxProject instanceof Project
374
+ ? setupStore.sandboxProject
375
+ : undefined;
376
+ const sandboxProjectId = sandboxProject?.projectId;
372
377
  const loadedProjectOptions = setupStore.projects
378
+ .filter((p) => p.projectId !== sandboxProjectId)
373
379
  .map(buildProjectOption)
374
380
  .sort(compareLabelFn);
375
381
  const loadedProjectIds = new Set(
376
382
  setupStore.projects.map((p) => p.projectId),
377
383
  );
378
384
  const recentProjectOptions: ProjectOption[] = setupStore.recentProjects
379
- .filter((r) => !loadedProjectIds.has(r.projectId))
385
+ .filter(
386
+ (r) =>
387
+ !loadedProjectIds.has(r.projectId) &&
388
+ r.projectId !== sandboxProjectId,
389
+ )
380
390
  .map((r) => {
381
391
  // Rebuild a real Project from the cached metadata; no synthetic
382
392
  // fields needed since we persist everything the schema requires.
@@ -390,6 +400,17 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
390
400
  return { label: stub.name, value: stub };
391
401
  });
392
402
  const projectOptions: ProjectOption[] = [
403
+ // The user's own sandbox project is fetched via a dedicated call
404
+ // (`loadSandboxProject`) and excluded from the main search results, so
405
+ // we surface it here as a labeled option pinned to the top of the list.
406
+ ...(sandboxProject
407
+ ? [
408
+ {
409
+ label: `${sandboxProject.name} (sandbox)`,
410
+ value: sandboxProject,
411
+ },
412
+ ]
413
+ : []),
393
414
  ...recentProjectOptions,
394
415
  ...loadedProjectOptions,
395
416
  ];
@@ -546,6 +567,10 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
546
567
  <div className="workspace-setup__body">
547
568
  <div className="activity-bar">
548
569
  <ActivityBarMenu />
570
+ {/* Spacer so the theme toggle sits at the bottom of the strip,
571
+ mirroring how the editor's activity bar is laid out. */}
572
+ <div className="activity-bar__items" />
573
+ <ColorThemeToggle />
549
574
  </div>
550
575
  <div
551
576
  className="workspace-setup__content"
package/src/index.ts CHANGED
@@ -64,6 +64,7 @@ export * from './stores/graph-modifier/DSL_Service_GraphModifierHelper.js';
64
64
  export * from './stores/graph-modifier/RawValueSpecificationGraphModifierHelper.js';
65
65
  export * from './stores/extensions/DSL_Mapping_LegendStudioApplicationPlugin_Extension.js';
66
66
  export * from './stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.js';
67
+ export * from './stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
67
68
  export * from './stores/extensions/DSL_Data_LegendStudioApplicationPlugin_Extension.js';
68
69
  export { DataProductEditorState } from './stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
69
70